0% found this document useful (0 votes)
3 views57 pages

Module 2 Notes

Uploaded by

Dhanya P
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views57 pages

Module 2 Notes

Uploaded by

Dhanya P
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

MODULE 2

Searching: Linear Search and Binary search.


Sorting: Bubble Sort, Selection Sort, Insertion Sort. Quicksort and Merge Sort (Recursive
Algorithms only) Complexity analysis of sorting algorithms (Detailed analysis is not
required) and Heap sort.
Hash Tables: Different Hash Functions: Division method, Multiplication Method, Mid Square
Method, Folding Method, Collision Resolution Techniques: Closed Hashing (Linear Probing)
-Drawbacks, Open Hashing (Separate Chaining). Remedies-Random Probing, Quadratic
Probing, Double Hashing. Load Factor and Re-hashing

SEARCHING

LINEAR SEARCH AND BINARY SEARCH

1.​ Linear search: Small & unsorted arrays


2.​ Binary search : Large arrays & sorted arrays
1.​ Linear Search
●​ It means looking at each element of the array, in turn, until you find the target
value.
Algorithm
●​ In the best case, the target value is in the first element of the array. So the search
takes some tiny, and constant, amount of time. Computer scientists denote this
O(1) In real life, we don’t care about the best case, because it so rarely actually
happens.
●​ In the worst case, the target value is in the last element of the array. So the search
takes an amount of time proportional to the length of the array. Computer
scientists denote this O(n)
●​ In the average case, the target value is somewhere in the array. So on average, the
target value will be in the middle of the array. So the search takes an amount of
time proportional to half the length of the array – also proportional to the length of
the array – O(n) again

2. Binary Search

The operation is performed on small list , and the list must be a sorted one.

• First we find the mid value of sorted list

mid value = (L+U)/2

• Let x is the searching element. If x < mid value, we consider only the left portion of the
list and we next consider that list. Then find the mid value of that list.

• Here , each time complexity can be reduced and list is contracted.

• If the searching element is less than the mid value, we consider lower bound only,
otherwise upper bound.

• The best case is O(1), because the searching element may be the mid value.

• The total list is reduced when divide each time. That is search space is reduced.

1st bisect = n/2

2nd bisect n/2² ……

𝑖 bisection, search space = n/2^i

Finally the search space become 1


• If n/2^i =1, then I = O(𝐥𝐨𝐠𝟐 𝒏)

• This is the worst and average time complexity .

• Binary searching is efficient than linear searching

●​ The general term for a smart search through sorted data is a binary search.

1.​ The initial search region is the whole array.


2.​ Look at the data value in the middle of the search region.
3.​ If you’ve found your target, stop.
4.​ If your target is less than the middle data value, the new search region is the
lower half of the data.
5.​ If your target is greater than the middle data value, the new search region is
the higher half of the data.
6.​ Continue from Step 2.
Algorithm
●​ Binary search reduces the work by half at each comparison
SORTING

Sorting is a technique to rearrange the elements of a list in ascending or descending


order. Sorting can be classified in two types;

Internal Sorts:-
This method uses only the primary memory during sorting process. All data items are
held in main memory and no secondary memory is required this sorting process. If all
the data that is to be sorted can be accommodated at a time in memory is called
internal sorting. There is a limitation for internal sorts; they can only process
relatively small lists due to memory constraints.

External Sorting

External Sorting is when all the data that needs to be sorted need not to be placed in
memory at a time, the sorting is called external sorting. External Sorting is used for
the massive amount of data. For example Merge sort can be used in external sorting as
the whole array does not have to be present all the time in memory,

BUBBLE SORT

Bubble Sort is the simplest sorting algorithm that works by repeatedly swapping the adjacent
elements if they are in the wrong order. This algorithm is not suitable for large data sets as its
average and worst-case time complexity are quite high.
●​ We sort the array using multiple passes. After the first pass, the maximum element
goes to end (its correct position). Same way, after second pass, the second largest
element goes to second last position and so on.
●​ In every pass, we process only those elements that have already not moved to
correct position. After k passes, the largest k elements must have been moved to
the last k positions.
●​ In a pass, we consider remaining elements and compare all adjacent and swap if
larger element is before a smaller element. If we keep doing this, we get the
largest (among the remaining elements) at its correct position.
SELECTION SORT

Selection Sort is a comparison-based sorting algorithm. It sorts an array

by repeatedly selecting the smallest (or largest) element from the

unsorted portion and swapping it with the first unsorted element. This

process continues until the entire array is sorted.

1.​ First we find the smallest element and swap it with the first

element. This way we get the smallest element at its correct

position.

2.​ Then we find the smallest among remaining elements (or second

smallest) and swap it with the second element.

3.​ We keep doing this until we get all elements moved to correct

position.
Time Complexity: O(n2) ,as there are two nested loops:(Same for best,

worst and average)


●​ One loop to select an element of Array one by one = O(n)

●​ Another loop to compare that element with every other Array

element = O(n)

●​ Therefore overall complexity = O(n) * O(n) = O(n*n) = O(n2)

INSERTION SORT

Insertion sort is a simple sorting algorithm that works by iteratively

inserting each element of an unsorted list into its correct position in a

sorted portion of the list. It is like sorting playing cards in your hands. You

split the cards into two groups: the sorted cards and the unsorted cards.

Then, you pick a card from the unsorted group and put it in the right place

in the sorted group.

●​ We start with the second element of the array as the first

element is assumed to be sorted.

●​ Compare the second element with the first element if the second

element is smaller then swap them.

●​ Move to the third element, compare it with the first two elements,

and put it in its correct position

●​ Repeat until the entire array is sorted.


Complexity Analysis of Insertion Sort

Time Complexity

●​ Best case: O(n), If the list is already sorted, where n is the

number of elements in the list.

●​ Average case: O(n2), If the list is randomly ordered


●​ Worst case: O(n2), If the list is in reverse order

QUICK SORT

QuickSort is a sorting algorithm based on the Divide and Conquer that

picks an element as a pivot and partitions the given array around the

picked pivot by placing the pivot in its correct position in the sorted array.

It works on the principle of divide and conquer, breaking down the

problem into smaller sub-problems.

There are mainly three steps in the algorithm:

1.​ Choose a Pivot: Select an element from the array as the pivot.

The choice of pivot can vary (e.g., first element, last element,

random element, or median).

2.​ Partition the Array: Rearrange the array around the pivot. After

partitioning, all elements smaller than the pivot will be on its left,

and all elements greater than the pivot will be on its right. The

pivot is then in its correct position, and we obtain the index of the

pivot.

3.​ Recursively Call: Recursively apply the same process to the two

partitioned sub-arrays (left and right of the pivot).

4.​ Base Case: The recursion stops when there is only one element

left in the sub-array, as a single element is already sorted.


Complexity Analysis of Quick Sort
Time Complexity:
●​ Best Case: (Ω(n log n)), Occurs when the pivot element divides

the array into two equal halves.

●​ Average Case (θ(n log n)), On average, the pivot divides the array

into two parts, but not necessarily equal.

●​ Worst Case: (O(n²)), Occurs when the smallest or largest

element is always chosen as the pivot (e.g., sorted arrays).

MERGE SORT

Merge sort is a popular sorting algorithm known for its efficiency and

stability. It follows the divide-and-conquer approach. It works by

recursively dividing the input array into two halves, recursively sorting the

two halves and finally merging them back together to obtain the sorted

array.

Step-by-step explanation of how merge sort works:

1.​ Divide: Divide the list or array recursively into two halves until it

can no more be divided.

2.​ Conquer: Each subarray is sorted individually using the merge

sort algorithm.

3.​ Merge: The sorted subarrays are merged back together in sorted

order. The process continues until all elements from both

subarrays have been merged.


Complexity Analysis of Merge Sort

●​ Time Complexity:
○​ Best Case: O(n log n), When the array is already sorted or

nearly sorted.

○​ Average Case: O(n log n), When the array is randomly

ordered.

○​ Worst Case: O(n log n), When the array is sorted in reverse

order.

HEAP SORT

Heap sort is a comparison-based sorting technique based on Binary Heap

Data Structure. It can be seen as an optimization over selection sort where

we first find the max (or min) element and swap it with the last (or first).

We repeat the same process for the remaining elements.

Heap Sort Algorithm


First convert the array into a max heap using heapify, Please note that this

happens in-place. The array elements are re-arranged to follow heap

properties. Then one by one delete the root node of the Max-heap and

replace it with the last node and heapify. Repeat this process while size of

heap is greater than 1.

●​ Rearrange array elements so that they form a Max Heap.

●​ Repeat the following steps until the heap contains only one

element:
○​ Swap the root element of the heap (which is the

largest element in current heap) with the last

element of the heap.

○​ Remove the last element of the heap (which is

now in the correct position). We mainly reduce

heap size and do not remove element from the

actual array.

○​ Heapify the remaining elements of the heap.

●​ Finally we get sorted array.

Detailed Working of Heap Sort

Step 1: Treat the Array as a Complete Binary Tree

We first need to visualize the array as a complete binary tree. For an array

of size n, the root is at index 0, the left child of an element at index i is at 2i

+ 1, and the right child is at 2i + 2.


Step 2: Build a Max Heap
Step 3: Sort the array by placing largest element at end of
unsorted array.
Complexity Analysis of Heap Sort
Time Complexity: O(n log n) (Same for best, worst and average)

What is Hash Table?


A Hash table is defined as a data structure used to insert, look up, and
remove key-value pairs quickly. It operates on the hashing concept, where
each key is translated by a hash function into a distinct index in an array.
The index functions as a storage location for the matching value. In simple
words, it maps the keys with the value.

What is Load factor?


A hash table's load factor is determined by how many elements are kept
there in relation to how big the table is. The table may be cluttered and
have longer search times and collisions if the load factor is high. An ideal
load factor can be maintained with the use of a good hash function and
proper table resizing.

What is a Hash function?


A Function that translates keys to array indices is known as a hash
function. The keys should be evenly distributed across the array via a
decent hash function to reduce collisions and ensure quick lookup speeds.

Choosing a hash function:

Selecting a decent hash function is based on the properties of the keys


and the intended functionality of the hash table. Using a function that
evenly distributes the keys and reduces collisions is crucial.

Criteria based on which a hash function is chosen:

●​ To ensure that the number of collisions is kept to a minimum, a

good hash function should distribute the keys throughout the

hash table in a uniform manner. This implies that for all pairings

of keys, the likelihood of two keys hashing to the same position in

the table should be rather constant.

●​ To enable speedy hashing and key retrieval, the hash function

should be computationally efficient.

●​ It ought to be challenging to deduce the key from its hash value.

As a result, attempts to guess the key using the hash value are

less likely to succeed.

●​ A hash function should be flexible enough to adjust as the data

being hashed changes. For instance, the hash function needs to

continue to perform properly if the keys being hashed change in

size or format.

Types of Hash Functions


There are many hash functions that use numeric or alphanumeric keys.

1.​ Division Method.

2.​ Multiplication Method

3.​ Mid-Square Method

4.​ Folding Method

1. Division Method
The division method involves dividing the key by a prime number and

using the remainder as the hash value.

h(k)=k mod m

k is the key value, and m is the size of the hash table.

It is best suited that M is a prime number as that can make sure the keys

are more uniformly distributed. The hash function is dependent upon the

remainder of a division.
Example:

k = 12345​
M = 95​
h(12345) = 12345 mod 95 ​
= 90

k = 1276​
M = 11​
h(1276) = 1276 mod 11 ​
=0
2. Mid Square Method:

The mid-square method is a very good hashing method. It involves two

steps to compute the hash value-

1.​ Square the value of the key k i.e. k2

2.​ Extract the middle r digits as the hash value.

Formula:

h(K) = h(k x k)

Here,​
k is the key value.

The value of r can be decided based on the size of the table.

Example:
Suppose the hash table has 100 memory locations. So r = 2 because two

digits are required to map the key to the memory location.

k = 60​
k x k = 60 x 60​
= 3600​
h(60) = 60

The hash value obtained is 60

Example:

3. Folding Method:

This method involves two steps:


1.​ Divide the key-value k into a number of parts i.e. k1, k2,

k3,….,kn, where each part has the same number of digits

except for the last part that can have lesser digits than the

other parts.

2.​ Add the individual parts. The hash value is obtained by

ignoring the last carry if any.

Formula:

k = k1, k2, k3, k4, ….., kn​


s = k1+ k2 + k3 + k4 +….+ kn​
h(K)= s

Here,​
s is obtained by adding the parts of the key k

Example:

k = 12345​
k1 = 12, k2 = 34, k3 = 5​
s = k1 + k2 + k3​
= 12 + 34 + 5​
= 51 ​
h(K) = 51

Example:
4. Multiplication Method

This method involves the following steps:

1.​ Choose a constant value A such that 0 < A < 1.


2.​ Multiply the key value with A.
3.​ Extract the fractional part of kA.
4.​ Multiply the result of the above step by the size of the hash
table i.e. M.
5.​ The resulting hash value is obtained by taking the floor of the
result obtained in step 4.

Formula:

h(K) = floor (M (kA mod 1))

Here,​
M is the size of the hash table.​
k is the key value.​
A is a constant value.

Example:
k = 12345​
A = 0.357840​
M = 100

h(12345) = floor[ 100 (12345*0.357840 mod 1)]​


= floor[ 100 (4417.5348 mod 1) ]​
= floor[ 100 (0.5348) ]​
= floor[ 53.48 ]​
= 53

Collision resolution techniques:

Collisions happen when two or more keys point to the same array index.

Chaining, open addressing, and double hashing are a few techniques for

resolving collisions.
OPEN HASHING

Open hashing, also known as separate chaining, is a collision resolution


technique used in hash tables within data structures. In this method, when
multiple keys hash to the same index (a collision occurs), instead of trying to
find another empty slot within the main hash table array, these colliding keys
are stored in a linked list (or another suitable data structure like a binary
search tree) at that specific index.

1) Separate Chaining

The idea behind Separate Chaining is to make each cell of the hash table
point to a linked list of records that have the same hash function value.
Chaining is simple but requires additional memory outside the table.
Example: We have given a hash function and we have to insert some
elements in the hash table using a separate chaining method for collision
resolution technique.

Hash function = key % 5,​


Elements = 12, 15, 22, 25 and 37.
CLOSED HASHING/ OPEN ADDRESSING

Open Addressing is a method for handling collisions. In Open Addressing,

all elements are stored in the hash table itself. So at any point, the size of

the table must be greater than or equal to the total number of keys (Note

that we can increase table size by copying old data if needed). This

approach is also known as closed hashing. This entire procedure is based

upon probing.

Type of closed hashing:

1. Linear Probing:

In linear probing, the hash table is searched sequentially that starts from

the original location of the hash. If in case the location that we get is

already occupied, then we check for the next location.

The function used for rehashing is as follows: rehash(key) =


(n+1)%table-size.

For example, The typical gap between two probes is 1 as seen in the

example below:

Let hash(x) be the slot index computed using a hash function and S be the
table size
If slot hash(x) % S is full, then we try (hash(x) + 1) % S​
If (hash(x) + 1) % S is also full, then we try (hash(x) + 2) % S​
If (hash(x) + 2) % S is also full, then we try (hash(x) + 3) % S

Example: Let us consider a simple hash function as “key mod 5” and a

sequence of keys that are to be inserted are 50, 70, 76, 85, 93.
Remedies for Clustering in Closed Hashing

To overcome the limitations of linear probing, especially clustering, other

probing strategies are used:

1)​ Quadratic Probing


Quadratic probing is an open addressing scheme in hashing for resolving

hash collisions in hash tables. Quadratic probing operates by taking the

original hash index and adding successive values of an arbitrary quadratic

polynomial until an open slot is found.

An example sequence using quadratic probing is:

H + 1 2 , H + 2 2 , H + 3 2 , H + 4 2 ...................... H + k 2

This method is also known as the mid-square method because in this

method we look for i2-th probe (slot) in i-th iteration and the value of i = 0,

1, . . . n – 1. We always start from the original hash location. If only the

location is occupied then we check the other slots.

Let hash(x) be the slot index computed using the hash function and n be

the size of the hash table.

If the slot hash(x) % n is full, then we try (hash(x) + 1 2 ) % n.​


If (hash(x) + 1 2 ) % n is also full, then we try (hash(x) + 2 2 ) % n.​
If (hash(x) + 2 2 ) % n is also full, then we try (hash(x) + 3 2 ) % n.​
This process will be repeated for all the values of i until an empty slot is
found
2)​ Double Hashing

Double hashing is a collision resolving technique in Open Addressed Hash

tables. Double hashing make use of two hash function,

●​ The first hash function is h1(k) which takes the key and gives out

a location on the hash table. But if the new location is not

occupied or empty then we can easily place our key.


●​ But in case the location is occupied (collision) we will use

secondary hash-function h2(k) in combination with the first

hash-function h1(k) to find the new location on the hash table.

This combination of hash functions is of the form

h(k, i) = (h1(k) + i * h2(k)) % n

where

●​ i is a non-negative integer that indicates a collision number,

●​ k = element/key which is being hashed

●​ n = hash table size.


3)​ Random Probing

In Random Probing, when a collision occurs, instead of checking the next


slot or following a fixed pattern (like in linear or quadratic probing), we use
a random sequence to choose the next slot.

●​ Formula:​
h'(key, i) = (h(key) + R(i)) % table_size​
where R(i) is a pseudo-random function (same sequence for the
same key).​

●​ This helps reduce clustering because it spreads keys more evenly


across the table.​

Example

●​ Hash table size: 10​

●​ Hash function: h(key) = key % 10​


●​ Keys to insert: 25, 35, 15, 95​

●​ Random sequence (fixed for this example): R(i) = [3, 7, 2, 5,


6]​
→ This means on the 1st probe we add 3, then 7, and so on.​

Step-by-Step Insertion:

1.​ Insert 25​


h(25) = 25 % 10 = 5​
Slot 5 is empty → insert at index 5​

2.​ Insert 35​


h(35) = 35 % 10 = 5​
Slot 5 is occupied → use random probing:​

○​ 1st probe: (5 + 3) % 10 = 8 → slot 8 is empty → insert at


8​

3.​ Insert 15​


h(15) = 15 % 10 = 5​
Slot 5 is taken → use random probing:​

○​ 2nd probe: (5 + 7) % 10 = 2 → slot 2 is empty → insert at


2​

4.​ Insert 95​


h(95) = 95 % 10 = 5​
Slot 5 is taken → use random probing:​

○​ 3rd probe: (5 + 2) % 10 = 7 → empty → insert at 7

What is Load Factor?

Load factor is a measure that helps to decide when to increase the HashTable or
HashMap capacity to maintain the operations(search and insert) complexity of O(1).
The default value of the load factor is 0.75, i.e. 75% of the HashMap size. Load
factor can be decided using the formula as follows:

The initial capacity of the HashMap * Load factor of the HashMap


Let's understand Load Factor with the help of an example. For example if the initial
capacity of Hashtable is 20 and the load factor of hastable is 0.75(default value),
then according to the formula 20*0.75 = 15. It means the 15th key-value pair of the
hashtable will keep its size as 20. Now when we insert the 16th key-value pair into
the hashtable, it will increases its size from 20 to 20*2 = 40 buckets.

Rehashing:​
Rehashing is the process of increasing the size of a hashmap and
redistributing the elements to new buckets based on their new hash
values. It is done to improve the performance of the hashmap and to
prevent collisions caused by a high load factor.

When a hashmap becomes full, the load factor (i.e., the ratio of the
number of elements to the number of buckets) increases. As the load
factor increases, the number of collisions also increases, which can lead to
poor performance. To avoid this, the hashmap can be resized and the
elements can be rehashed to new buckets, which decreases the load
factor and reduces the number of collisions.

During rehashing, all elements of the hashmap are iterated and their new
bucket positions are calculated using the new hash function that
corresponds to the new size of the hashmap. This process can be
time-consuming but it is necessary to maintain the efficiency of the
hashmap.

Why rehashing?

Rehashing is needed in a hashmap to prevent collision and to maintain the


efficiency of the data structure.

As elements are inserted into a hashmap, the load factor (i.e., the ratio of
the number of elements to the number of buckets) increases. If the load
factor exceeds a certain threshold (often set to 0.75), the hashmap
becomes inefficient as the number of collisions increases. To avoid this, the
hashmap can be resized and the elements can be rehashed to new
buckets, which decreases the load factor and reduces the number of
collisions. This process is known as rehashing.

Rehashing can be costly in terms of time and space, but it is necessary to


maintain the efficiency of the hashmap.

How Rehashing is done?​


Rehashing can be done as follows:

●​ For each addition of a new entry to the map, check the load

factor.

●​ If it's greater than its pre-defined value (or default value of 0.75 if

not given), then Rehash.

●​ For Rehash, make a new array of double the previous size and

make it the new bucketarray.

●​ Then traverse to each element in the old bucketArray and call the

insert() for each so as to insert it into the new larger bucket array.

You might also like