Merge, Quick Sort and Searching
Merge, Quick Sort and Searching
Email: [email protected]
SEARCHING
Linear Search
• Linear search is used to search a key element from multiple elements.
• Algorithm:
• Step 1: Traverse the array.
• Step 2: Match the key element with array element.
• Step 3: If key element is found, return the index position of the array
element.
• Step 4: If key element is not found, return -1.
Linear Search
• How Linear Search Works?
• The following steps are followed to search for an element k = 1 in the
list below.
Linear Search
• How Linear Search Works?
• Start from the first element, compare k with each element x.
Linear Search
• How Linear Search Works?
• If x == k, return the index.
• Since the item we were looking for is found, position 6 will be returned.
Interpolation Search
SORTING
MergeSort
Merge sort is a sorting algorithm that follows the divide-and-conquer
approach. It works by recursively dividing the input array into smaller
subarrays and sorting those subarrays then merging them back together to
obtain the sorted array.
In simple terms, we can say that the process of merge sort is to divide the
array into two halves, sort each half, and then merge the sorted halves back
together. This process is repeated until the entire array is sorted.
21
Divide-and-Conquer
• Divide the problem into a number of sub-problems
• Similar sub-problems of smaller size
22
MergeSort Algorithm
23
Merge-Sort Tree
An execution of merge-sort is depicted by a binary tree
– each node represents a recursive call of merge-sort and stores
unsorted sequence before the execution and its partition
sorted sequence at the end of the execution
– the root is the initial call
– the leaves are calls on subsequences of size 0 or 1
7 29 4 2 4 7 9
72 2 7 94 4 9
24
Execution Example
Partition
7 2 9 43 8 6 1
25
Execution Example (cont.)
7 29 4
26
Execution Example (cont.)
Recursive call, partition
7 2 9 43 8 6 1
7 29 4
72
27
Execution Example (cont.)
Recursive call, base case
7 2 9 43 8 6 1
7 29 4
72
77
28
Execution Example (cont.)
Recursive call, base case
7 2 9 43 8 6 1
7 29 4
72
77 22
29
Execution Example (cont.)
Merge
7 2 9 43 8 6 1
7 29 4
722 7
77 22
30
Execution Example (cont.)
Recursive call, …, base case, merge
7 2 9 43 8 6 1
7 29 4
722 7 9 4 4 9
31
Execution Example (cont.)
Merge
7 2 9 43 8 6 1
7 29 4 2 4 7 9
722 7 9 4 4 9
32
Execution Example (cont.)
Recursive call, …, merge, merge
7 2 9 43 8 6 1
7 29 4 2 4 7 9 3 8 6 1 1 3 6 8
722 7 9 4 4 9 3 8 3 8 6 1 1 6
33
Execution Example (cont.)
Merge
7 2 9 43 8 6 1 1 2 3 4 6 7 8 9
7 29 4 2 4 7 9 3 8 6 1 1 3 6 8
722 7 9 4 4 9 3 8 3 8 6 1 1 6
34
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.
Applications, advantages and
disadvantages
• Applications of Merge Sort:
• Sorting large datasets
• External sorting (when the dataset is too large to fit in memory)
• Inversion counting ( SS SAVED)
• It is a preferred algorithm for sorting Linked lists.
• It can be easily parallelized as we can independently sort subarrays
and then merge.
Continued
• Advantages of Merge Sort:
• Stability : Merge sort is a stable sorting algorithm, which means it maintains the relative
order of equal elements in the input array.
• Simple to implement: The divide-and-conquer approach is straightforward.
• Disadvantages of Merge Sort:
• Space complexity: Merge sort requires additional memory to store the merged sub-
arrays during the sorting process.
• Not in-place: Merge sort is not an in-place sorting algorithm, which means it requires
additional memory to store the sorted data. This can be a disadvantage in applications
where memory usage is a concern.
• Slower than QuickSort in general. QuickSort is more cache friendly because it works in-
place.
Merge Algorithm
• The basic merging algorithms takes
• And three counters aptr, bptr and cptr. (initially set to the beginning of their
respective arrays)
• The smaller of A[aptr] and B[bptr] is copied to the next entry in C i.e.
C[cptr].
• The appropriate counters are then advanced.
• When either of input list is exhausted, the remainder of the other list
is copied to C.
Merge Algorithm
A[] B[]
14 56 66 108 12 34 89
aptr bptr
14 > 12 therefore
C[cptr] = 12 If A[aptr] < B[bptr]
C[cptr++] = A[aptr++]
C[] Else
C[cptr++] = B[bptr++]
12
cptr
Merge Algorithm
A[] B[]
14 56 66 108 12 34 89
aptr bptr
14 > 12 therefore
C[cptr] = 12 If A[aptr] < B[bptr]
cptr++ C[cptr++] = A[aptr++]
C[] Else
C[cptr++] = B[bptr++]
12
cptr
Merge Algorithm
A[] B[]
14 56 66 108 12 34 89
aptr bptr
14 > 12 therefore
C[cptr] = 12 If A[aptr] < B[bptr]
cptr++ C[cptr++] = A[aptr++]
C[] bptr++ Else
C[cptr++] = B[bptr++]
12
cptr
Merge Algorithm
A[] B[]
14 56 66 108 12 34 89
aptr bptr
14 < 34 therefore
C[cptr] = 14 If A[aptr] < B[bptr]
C[cptr++] = A[aptr++]
C[] Else
C[cptr++] = B[bptr++]
12 14
cptr
Merge Algorithm
A[] B[]
14 56 66 108 12 34 89
aptr bptr
14 < 34 therefore
C[cptr] = 14 If A[aptr] < B[bptr]
cptr++ C[cptr++] = A[aptr++]
C[] Else
C[cptr++] = B[bptr++]
12 14
cptr
Merge
A[]
Algorithm
B[]
14 56 66 108 12 34 89
aptr
bptr
14 < 34 therefore
C[cptr] = 14 If A[aptr] < B[bptr]
cptr++ C[cptr++] = A[aptr++]
C[] aptr++ Else
C[cptr++] = B[bptr++]
12 14
cptr
Merge Algorithm
A[] B[]
14 56 66 108 12 34 89
aptr
bptr
56 > 34 therefore
C[cptr] = 34 If A[aptr] < B[bptr]
C[cptr++] = A[aptr++]
C[] Else
C[cptr++] = B[bptr++]
12 14 34
cptr
Merge
A[]
Algorithm
B[]
14 56 66 108 12 34 89
aptr
bptr
56 > 34 therefore
C[cptr] = 34 If A[aptr] < B[bptr]
cptr++ C[cptr++] = A[aptr++]
C[] Else
C[cptr++] = B[bptr++]
12 14 34
cptr
Merge Algorithm
A[] B[]
14 56 66 108 12 34 89
aptr
bptr
56 > 34 therefore
C[cptr] = 34 If A[aptr] < B[bptr]
cptr++ C[cptr++] = A[aptr++]
C[] bptr++ Else
C[cptr++] = B[bptr++]
12 14 34
cptr
Merge
A[]
Algorithm
B[]
14 56 66 108 12 34 89
aptr
bptr
56 < 89 therefore
C[cptr] = 56 If A[aptr] < B[bptr]
C[cptr++] = A[aptr++]
C[] Else
C[cptr++] = B[bptr++]
12 14 34 56
cptr
Merge
A[] Algorithm B[]
14 56 66 108 12 34 89
aptr
bptr
56 < 89 therefore
C[cptr] = 56 If A[aptr] < B[bptr]
cptr++ C[cptr++] = A[aptr++]
C[] Else
C[cptr++] = B[bptr++]
12 14 34 56
cptr
Merge
A[]
Algorithm
B[]
14 56 66 108 12 34 89
aptr
bptr
56 < 89 therefore
C[cptr] = 56 If A[aptr] < B[bptr]
cptr++ C[cptr++] = A[aptr++]
C[] aptr++ Else
C[cptr++] = B[bptr++]
12 14 34 56
cptr
Merge
A[]
Algorithm
B[]
14 56 66 108 12 34 89
aptr
bptr
66 < 89 therefore
C[cptr] = 66 If A[aptr] < B[bptr]
C[cptr++] = A[aptr++]
C[] Else
C[cptr++] = B[bptr++]
12 14 34 56 66
cptr
Merge
A[]
Algorithm
B[]
14 56 66 108 12 34 89
aptr
bptr
66 < 89 therefore
C[cptr] = 66 If A[aptr] < B[bptr]
cptr++ C[cptr++] = A[aptr++]
C[] Else
C[cptr++] = B[bptr++]
12 14 34 56 66
cptr
Merge
A[]
Algorithm
B[]
14 56 66 108 12 34 89
aptr
bptr
66 < 89 therefore
C[cptr] = 66 If A[aptr] < B[bptr]
cptr++ C[cptr++] = A[aptr++]
C[] aptr++ Else
C[cptr++] = B[bptr++]
12 14 34 56 66
cptr
Merge Algorithm
A[] B[]
14 56 66 108 12 34 89
aptr
bptr
108 > 89 therefore
C[cptr] = 89 If A[aptr] < B[bptr]
C[cptr++] = A[aptr++]
C[] Else
C[cptr++] = B[bptr++]
12 14 34 56 66 89
cptr
Merge Algorithm
A[] B[]
14 56 66 108 12 34 89
aptr
bptr
108 > 89 therefore
C[cptr] = 89 If A[aptr] < B[bptr]
cptr++ C[cptr++] = A[aptr++]
C[] Else
C[cptr++] = B[bptr++]
12 14 34 56 66 89
cptr
Merge
A[]
Algorithm
B[]
14 56 66 108 12 34 89
aptr
bptr
108 > 89 therefore
C[cptr] = 89 If A[aptr] < B[bptr]
cptr++ C[cptr++] = A[aptr++]
C[] bptr++ Else
C[cptr++] = B[bptr++]
12 14 34 56 66 89
cptr
Merge
A[]
Algorithm
B[]
14 56 66 108 12 34 89
aptr
bptr
Array B is now
finished, copy If A[aptr] < B[bptr]
remaining elements of
C[cptr++] = A[aptr++]
array A in array C
C[] Else
C[cptr++] = B[bptr++]
12 14 34 56 66 89
cptr
Merge Algorithm
A[] B[]
14 56 66 108 12 34 89
aptr
bptr
Array B is now
finished, copy If A[aptr] < B[bptr]
remaining elements of
C[cptr++] = A[aptr++]
array A in array C
C[] Else
C[cptr++] = B[bptr++]
12 14 34 56 66 89 108
cptr
Example – n Not a Power of 2
1 2 3 4 5 6 7 8 9 10 11
4 7 2 6 1 4 7 3 5 2 6 q=6
Divide
1 2 3 4 5 6 7 8 9 10 11
q=3 4 7 2 6 1 4 7 3 5 2 6 q=9
1 2 3 4 5 6 7 8 9 10 11
4 7 2 6 1 4 7 3 5 2 6
1 2 3 4 5 6 7 8 9 10 11
4 7 2 6 1 4 7 3 5 2 6
1 2 4 5 7 8
4 7 6 1 7 3
59
Example – n Not a Power of 2
1 2 3 4 5 6 7 8 9 10 11
Conquer 1 2 2 3 4 4 5 6 6 7 7
and
Merge
1 2 3 4 5 6 7 8 9 10 11
1 2 4 4 6 7 2 3 5 6 7
1 2 3 4 5 6 7 8 9 10 11
2 4 7 1 4 6 3 5 7 2 6
1 2 3 4 5 6 7 8 9 10 11
4 7 2 1 6 4 3 7 5 2 6
1 2 4 5 7 8
4 7 6 1 7 3
60
Merge - Pseudocode 1
p
2 3 4
q
5 6 7 8
r
2 4 5 7 1 2 3 6
Alg.: MERGE(A, p, q, r)
1. Compute n1 and n2 n1 n2
3. L[n1 + 1] ← ; R[n2 + 1] ← L 2 4 5 7
4. i ← 1; j ← 1 q+1 r
5. for k ← p to r R 1 2 3 6
6. do if L[ i ] ≤ R[ j ]
7. then A[k] ← L[ i ]
8. i ←i + 1
9. else A[k] ← R[ j ] 61
Merge Sort in C++
62
Quick Sort
• Fastest known sorting algorithm in practice
• Average case: O(N log N)
• Worst case: O(N2)
• But the worst case can be made exponentially
unlikely.
• Another divide-and-conquer recursive
algorithm, like merge sort.
QuickSort Design
• Follows the divide-and-conquer paradigm.
• Divide: Partition (separate) the array A[p..r] into two (possibly
empty) subarrays A[p..q–1] and A[q+1..r].
• Each element in A[p..q–1] < A[q].
• A[q] < each element in A[q+1..r].
• Index q is computed as part of the partitioning procedure.
• Conquer: Sort the two subarrays by recursive calls to quicksort.
A[p..q – 1] A[q+1..r]
Partition
5
5 5
Issues To Consider
• How to pick the pivot?
• Many methods (discussed later)
• How to partition?
• Several methods exist.
• The one we consider is known to give good results and
to be easy and efficient.
• We discuss the partition strategy first.
Partitioning Strategy
• For now, assume that pivot = A[(left+right)/2].
• We want to partition array A[left .. right].
• First, get the pivot element out of the way by swapping it
with the last element (swap pivot and A[right]).
• Let i start at the first element and j start at the next-to-last
element (i = left, j = right – 1)
swap
5 7 4 6 3 12 19 5 7 4 19 3 12 6
pivot i j
Partitioning Strategy
• Want to have
• A[k] pivot, for k < i pivot pivot
• A[k] pivot, for k > j
• When i < j
i j
• Move i right, skipping over elements smaller than the pivot
• Move j left, skipping over elements greater than the pivot
• When both i and j have stopped
• A[i] pivot
• A[j] pivot A[i] and A[j] should now be swapped
5 7 4 19 3 12 6 5 7 4 19 3 12 6
i j i j
Partitioning Strategy (2)
• When i and j have stopped and i is to the left of j (thus legal)
• Swap A[i] and A[j]
• The large element is pushed to the right and the small element is
pushed to the left
• After swapping
• A[i] pivot
• A[j] pivot
• Repeat the process until i and j cross
swap
5 7 4 19 3 12 6 5 3 4 19 7 12 6
i j i j
Partitioning Strategy (3)
5 3 4 19 7 12 6
• When i and j have crossed
• swap A[i] and pivot
i j
• Result:
• A[k] pivot, for k < i 5 3 4 19 7 12 6
• A[k] pivot, for k > i
j i
swap A[i] and pivot
5 3 4 6 7 12 19
Break!
j i
Picking the Pivot
• There are several ways to pick a pivot.
• Median-of-three partitioning
• eliminates the bad case for sorted input.
Median of Three Method
• Compare just three elements: the leftmost, rightmost and
center
• Swap these elements if necessary so that
• A[left] = Smallest
• A[right] = Largest
• A[center] = Median of three
• Pick A[center] as the pivot.
• Swap A[center] and A[right – 1] so that the pivot is at the second last position (why?)
Median of Three: Example
A[left] = 2, A[center] = 13,
2 5 6 4 13 3 12 19 6
A[right] = 6
pivot
pivot
We only need to partition A[ left + 1, …, right – 2 ]. Why?
Quicksort for Small Arrays
• For very small arrays (N<= 20), quicksort does not perform
as well as insertion sort
• A good cutoff range is N=10
• Switching to insertion sort for small arrays can save about
15% in the running time
Mergesort vs Quicksort
• Both run in O(n*logn)
• Mergesort – always.
• Quicksort – on average
• Compared with Quicksort, Mergesort has less number of
comparisons but larger number of moving elements
• In Java, an element comparison is expensive but moving elements is
cheap. Therefore, Mergesort is used in the standard Java library for
generic sorting