2. Divide and Conquer
2. Divide and Conquer
The divide and conquer strategy involves 2. Conquer: By algorithm given below.
three main steps. a) Finding maximum subarray of A[l…m]
and A[m+1…h]
y Step 1: Problem is divided into some
b) Finding a maximum subarray that
subproblems that are smaller parts of the
crosses the midpoint.
same problem.
3. Combine: Returning the maximum of the
y Step 2: Solve (Conquer) the subproblems
above tree.
in a recursive manner. If the subproblem is
This process of finding solution works
small, then solve it directly.
because any subarray may lie completely on
y Step 3: Combine solutions of all the
one side of the midpoint, on the other side,
subproblems into the original problem’s
or crossing the midpoint.
solution.
y When the subproblem is not small Algorithm:
enough to find solution directly (without FIND-MAXIMUM-CROSSING-SUBARRAY (Arr,
recursion), i.e., with base case, it is divided l, m, h)
into smaller subproblems.
l-sum = −∞
The Maximum Subarray Problem sum = 0
Problem: for i ¬ m downto l
Given an array A[1...n], one should find a sum ¬ sum + Arr[i]
continuous subarray that sums up to the
if sum > l-sum
maximum value.
l-sum ¬ sum
Input: Array A[1...n]
max-left ¬ i
Output:
r-sum ¬ – ∞
The maximum sum of values possible in the
subarray A[i...j] and the indices i and j. sum ¬ 0
for j ¬ m + 1 to h
Note:
Maximum subarray might not be unique. sum ¬ sum + Arr[j]
if sum > r-sum
Example:
r-sum ¬ sum
Array 5 −4 3 4 −2 1 max-right ¬ j
A
return (max-left, max-right, l-sum + r-sum)
1 2 3 4 5 6 Algorithm returns the indices i and j and the
Maximum subarray: A[1…4] (i = 1, j = 4) and sum of two subarrays.
sum = 8. y If the subarray Arr[l…h] contain n entries,
then n = h–l+1.
Divide and Conquer Strategy: y Since, each iteration of two for loops takes
ivide: The array A[l…h] is divided into two
1. D θ(1) time, and the number of iterations
subarrays of as equal size as possible by is to be counted to get the total time
calculating the mid. complexity.
So, the time complexity can be given as By master’s theorem, the time complexity is
O(nlog2 7), which is approximately O(n2.8074)
θ 1() if n =
1
NOTE:
()
T n =
( ) ( )
8T n / 2 + θ n
2
if n > 1 Strassen’s method is not suggested for
practical purposes due to the following
By masters theorem, the time complexity is
reasons:
O(n3), which is the same as the naive method.
y More number of constants are used
Strassen’s method: and naive methods performs better in
y Strassen’s method makes the recursion most of the cases.
tree sparse. y There are better methods available for
y Instead of eight recursive multiplications of sparse matrices in particular.
n/2x n/2 matrices, it performs only seven. y The sub-matrices occupy more space
y Strassen’s method also uses the divide in recursion method.
and conquer strategy, i.e., it divides the y Strassen’s algorithm gives more errors
matrix into some smaller matrices of due to precision limitations in computer
order n/2 x n/2. arithmetic on non-integer value.
Example:
Analysis: Combine:
y Even the Merge sort works correctly on MERGE_FUNCTION procedure applied on an
odd-length arrays. Recurrence analysis is n-sized subarray takes time θ(n).
simplified if we assume the input array
Recurrence for the worst-case running time
length is of power of 2.
T(n) of merge sort.
Break down of running time:
Divide: θ(1) if n =
1
T(n) = n
This step finds the middle of the subarray in 2T 2 + θ(n) if n > 1
constant time, i.e., O(1).
Conquer: By Masters Theorem:
Combine:
(G)
Since all the subarrays are already sorted,
no extra work is needed to combine them
together. Array A[p...r] is now sorted. (H)
(I)
y When the first iteration is done, then one = 2∑ ( T(i − 1)) + (n − 1)n ...(i)
nT(n) ...(1)
i= 1
subarray contains 0 elements, and the
other subarray has n-1 elements. For (n-1)
y Quicksort is applied recursively on this n− 1
n− 1
− 1) 2∑ ( T(i − 1)) + (n − 1)(n − 2)
(n − 1)T(n= ...(2)
...(ii)
i= 1
nT(n) − (n − 1)T(n − 1)
= 2T(n − 1) + 2(n − 1)
Note:
Randomisation is a general tool to improve this is because atleast (n-1)
algorithms with bad worst-case but good comparison is required.
or average-case complexity.
Hence,
Randomised_Partition (Arr, p, r)
1 n
{ T(n)
= ∑ ( T( j − 1) + T(n − j) + n − 1)
n j= 1
i Random(p, r) /* it generate a
random number (replacing i with j for simplicity)
between p = This is the expectation.
and r and including
p and r */ n
∑ T( j − 1) ==
This quantity varies
this quantity variesfrom
fromT(0)
T(0)to
toT(n-1)
T(n-1)
exchange Arr[r] Arr[i] j= 1
} ∑ T(n − j)==
j= 1
this quantity
This quantity varies
varies from
from T(n-1)
T(n-1) to
to T(0)
T(0)
Randomised_Quicksort (Arr, p, r)
So, every term T(0) to T(n-1) appears twice.
{
So, we write it as:
if p < r
2 n− 1 1 n
q Randomised_Partition (Arr, p, r)
=
=∑
n j 0=
T( j) + ∑ n − 1
n j1
Randomised_Quicksort (Arr, p, q-1)
Randomised_Quicksort (Arr, q+1, r) 2 n− 1 n(n − 1)
} = ∑
n j=0
T( j) +
n
Time complexity:
y Let’s assume T(n) be the number of= 2 ∑ T( j) + n − 1
n− 1
...(3)
comparisons needed to sort n numbers n j=0
using quicksort.
y Since each split occurs with probability = O(nlog 2 n)
1/n. In quicksort, every number requires
atleast (n-1) comparison because the (n-1) \ The expected number of comparisons is
number of elements has to be compared O(nlogn). But the worst-case time complexity
with the pivot element. of randomised quicksort is O(n2).
n+ 1 1 1
Note that T(0) = 0, T(1) = 0. <
T(n − 3) + 2(n + 1) + +2
(n − 2) n (n − 1)
2 n− 2
Now,=
T(n − 1) ∑ T(i) + (n − 2)
(n − 1) i=0 <
n + 1 n − 3 + 1
1
T(n − 4) + 2 + 2(n + 1) +
1
+2
(n − 2) n − 3 n (n − 1)
2 < n−n2 + 1 n − 3 + 1 1
T(n − 4) + 2 + 2(n + 1) +
1
or, (T(n − 1) − n + 2) = ∑ T(i) +2
(n − 1) i(n
=0
− 2) n − 3 n (n − 1)
n+ 1 n−2 1 1
Multiply both sides by (n − 1) < T(n − 4) + 2 + 2(n + 1) + +2
n (n − 2) (n − 3) n (n − 1)
n+ 1 n−2 1 1
2 n− 2 (n − 1) (n − <
1) T(n − 4) + 2 + 2(n + 1) + +2
∑( ) n
(n − 1) i=0
T(i) × = × ( T(n − 1)
n (n − 2) (n − 3)
− n + 2 ) n (n − 1)
2 n− 2 (n − 1) n+ 1 2(n + 1) 1 1
or, ∑ T(i)= n =
n i=0
( T(n − 1) − n + 2) ...(ii)
...(2) <
(n − 3)
T(n − 4) +
(n − 2)
+ 2(n + 1) + +2
n (n − 1)
n − 1 + 2 2(n − 1) (n + 1) 1 1 1 1
= T(n − 1) −n+ 1+ + (n − 1) < T(2) + 2(n + 1) + + + ... + + 2
n n (n − (n + 3)) n (n − 1) (n − 2) 4
(n + 1) 2(n − 1)
= T(n − 1) + (n + 1) 1 1 1 1
n n < T(2) + 2(n + 1) + + + ... + + 2
(n − (n + 3)) n (n − 1) (n − 2) 4
n+ 1 2(n − 1)
So, we got = T(n) T(n − 1) +
n n
y Max–Heapify Pseudocode
We have n / 4 nodes with level logn
MAX–HEAPIFY(Arr, i)
1. l ¬ left_node i) We have n / 8 nodes with level logn–1,
2. r ¬ right_node i)
3. if (l ≤ Arr.size_of_heap && Arr[i]<Arr[l])
4. maximum ¬ l We have 1 root node that is 0 level above the
5. else maximum ¬ i leaves.
6. if (r ≤ Arr.size_of_heap && Arr[maximum] y Total amount of work in BUILD–MAX–HEAP
< Arr[r]) for loop can be summed as
7. maximum ¬ r n/4 (1C) + n/8 (2C) + n/16 (3C) + … + 1(lognC)
8. if (maximum ≠ i) substitute n/4 = 2K
9. swap Arr[i] and Arr[maximum] C 2K (1/20 + 2/21 + 3/22 + …(K+1)/2K)
10. MAX–HEAPIFY (Arr, maximum) The term in brackets is bounded by a
y Convert array A[1…n] into a max–heap, constant.
where n=A.length. y This means that build–max–heap is O(n).
y The elements in the subarray
Build–max–heap (example):
A ( n / 2 + 1) ...n are all leaves of the tree,
A 4 1 3 2 16 9 10 14 8 7
and so each is a 1–element heap to begin
with.
1 4
3 MAX –HEAPIFY (A, 5)
2
Build–max–heap (a): 1 3 No change
4 5 7
1. A. size_of_heap = A. length 2
6 MAX –HEAPIFY (A, 4)
16 9 10
2. for i = A.length / 2 down to 1 8 9 10
Swap A[4] and A[8]
14 8 7
3. MAX–HEAPIFY (A, i)
1 4 MAX–HEAPIFY (A, 2)
2 3
1 10 Swap A[2] and A[5]
4 5 6 7 Swap A[5] and A[10]
14 16 9 3
8 9 10
2 8 7
1 4 MAX–HEAPIFY (A, 1)
2 3
Swap A[1] with A[2]
16 10
4 5 6 7 Swap A[2] with A[4]
14 7 9 3 Swap A[4] with A[9]
8 9 10
2 8 1
1 16
2 3
14 10
A 4 1 3 2 16 9 10 14 8 7 4 7
5 6
8 7 9 3
8 9 10
2 4 1
MAX–HEAPIFY (A, 1) 1 10
2 3
8 9
4 5 6 7
1 14 4 7 1 3
2 3 8
8 10 2
4 5 6 7 _________________________
4 7 9 3 ______
8 9
2 1
_________________________
______
1 10 Running time:
2 3
8 9 y The heap is empty after n iterations.
4 5 6 7
y Each iteration does a swap and a max-
4 7 1 3
8 heapify operation, which involves O(logn)
2 time.
Swap A[8] and A[1] y So it takes O(nlogn) in total.
Note:
1 2 Heapsort is an efficient algorithm and has
2 3 its own uses, but quicksort beats it, if it is
8 9 implemented correctly.
4 5 6 7
4 7 1 3 y The most commonly used application of
8 9 10
10 14 16 Not part of heap heaps is priority queues.
Note:
Time–taken to perform some operations in Max–heap is given below
Fing max Delete max Insert Increase Decrease key Find min Search Delete
1 2 3 4 5 6
(E) 1 2 4 5 6 3 NOTE:
INSERTION_SORT gives time complexity
1 2 3 4 5 6 depending on how nearly the input
(F) 1 2 3 4 5 6 sequence is sorted. It is better for almost
Analysis of Insertion Sort sorted sequences.
Worst-case behaviour on an array of n length:
y Inner loop could be executed ‘i’ times Previous Years’ Question
y ‘i’ swaps per loop ⇒ O(n2) total swaps
Consider the following array
23 32 45 69 72 73 89 97
Rack Your Brain
Which algorithm out of the following options
uses the least number of comparisons
What sort of input leads to the worst case
(among the array elements) to sort the
time complexity in insertion sort?
above array in ascending order?
Best case behaviour on an array of length ‘n’ [CS-2021 (Set-1)]
(A) Selection sort
y When the input array is sorted (B) Merge sort
y Inner loop executed 0 times ⇒ 0 swaps (C) Insertion sort
y While condition is entry condition (D) Q
uicksort using the last element as
(always performed at least once) pivot
So, O(n) comparisons are in the best case to Solution: (C)
verify the array is indeed sorted.
Bubble sort:
Binary Insertion Sort Bubble sort is a popular yet inefficient sorting
BINARY–INSERTION–SORT (A, n) algorithm.
for j ← 2 to n y It works by repeatedly swapping adjacent
insert A[j] Key into the (already sorted) elements that are out of order.
sub–array A[1...j–1]
use binary search to find its right position Bubble sort (Arr)
y Binary search will take O(logn) time. 1. for i ¬ 1 up to Arr. length – 1
However, shifting the elements after 2. for j ¬ Arr. length down to i + 1
insertion will still take O(n) time 3. if Arr [j] < Arr [j – 1]
4. swap Arr[j] and Arr[j–1]
The algorithm:
Find minimum element in A[1...4] and place it Input
at the beginning of A[1...4]
Array [1…n], where A[j] ∈ [1,2,3…k]
Output
The array [1…n] holds the sorted output and
Find minimum element in A[2...4] and place the array count[0…k] provides temporary
it at the beginning of A[2...4] working storage.
Counting_sort (array, array_size):
1. Max ← maximum element in the array
and create an array Count of size max and
Find minimum element in A[3...4] and place initialize with zeroes
it at the beginning of A[3...4] 2. For j ← 0 to size
3. Find the total count of each unique
element and store the count at jth index
in count array
Pseudocode of selection sort: 4. For i ← 1 to max
5. Find the cumulative sum and store it in
Sleection–sort (A): count array itself
for j ← 1 to n–1 6. For j ← size down to 1 restore the elements
smallest ← j to array
for i ← j + 1 to n 7. Decrement count_arr[arr[j]] and make
if A[i] < A[smallest] arr[count_arr[arr[j]]]=j
1 2 3 4 5 6 7 8
(A) 2 5 3 0 2 3 0 3
0 1 2 3 4 5 0 1 2 3 4 5
(C) 2 0 2 3 0 1 (C) 2 2 4 7 7 8
(A) (B)
1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8
(B) 3 (B) 0 3
0 1 2 3 4 5 0 1 2 3 4 5
(C) 2 2 4 6 7 8 (C) 1 2 4 6 7 8
(C) (D)
1 2 3 4 5 6 7 8
(B) 0 3 3
0 1 2 3 4 5 1 2 3 4 5 6 7 8
(C) 1 2 4 5 7 8 (B) 0 0 2 2 3 3 3 5
(E) (F)
Finding the maximum element takes O(n) y Bucket sort assumes the input is given a
time. uniform distribution function and runs in
O(n) time.
Creating an array and initializing it to
zeroes takes O(k). therefrore, total time y Same as counting sort, bucket sort is also
complexity=O(n+k) space complexity=O(k) fast since it assumes something about the
for creating new array. input.
y Counting sort thinks that the input
Since the elements are changed in-place,it is consists of n integers in a smaller range,
an inplace algorithm whereas bucket sort assumes that the
input is generated by a random process
that distributes elements in the array
uniformly and independently over an
Rack Your Brain interval [0, 1).
y Bucket sort divides the [0, 1) interval into n
Why don’t we always use counting sort? equal-sized intervals (known as buckets)
Hint: Depends on the range K of elements. and distributes n input elements into the
buckets.
Bucket Sort y Since the numbers are uniformly and
y Counting sort assumes every element in independently distributed over [0, 1), the
the array of size n is in the range 0 to k, chances of getting more elements in the
where k is an integer. same bucket are very, very less.
Radix-sort:
y Alike counting and bucket sorting
algorithms, even radix-sort assumes that
the input has some information.
y It assumes that the input values are to
be sorted from the base, i.e., means all
numbers in the array have d digits.
y In radix sort, sorting done with each digit
from the last to the first.
Time Complexity y A stable sort algorithm is used to sort all
Best Case the numbers by least significant, then last
but one, so on.
y In case if all the elements are uniformly
y Counting sort gives a time complexity of
distributed, then every insertion take O(1)
O(nd) O(n) for this procedure in radix sort.
return '6'
Note:
Binary search and hash tables are used
more significantly in practice compared
y Let n d-digited numbers be given, in which to Linear search since they allow faster
each digit can take k values possible. searching.
y RADIX-SORT sorts the input numbers
Binary Search
correctly in (d(n+k)) time. If the
intermediate stable algorithm used to sort y Binary search is one of the most efficient
the numbers takes d(n+k). search algorithms available.
y When each digit is between 0 to k-1 and k is y It is based on the divide-conquer strategy.
not too large, counting sort is the best choice.
Note:
Then each pass over n d-digited numbers
Binary search algorithms can be applied
takes (n+k). Such passes are d. Therefore,
only on sorted arrays.
radix sort takes (d(n+k)) time in total.
y As a result, the elements must be
y When d is constant and K = O(n), we can
arranged in a certain order.
make radix sort run in linear time. More
y If the elements are numbers, use either
generally, we have some flexibility in how
ascending or descending order.
to break each key into digits.
y If the elements are strings, use
Searching dictionary order.
y Searching is a process of locating a specific To use binary search on an array that is not
element among a set of elements. sorted.
y If the required element is found, the search
y Sort the array first using the sorting
is considered successful. Otherwise, it is
technique.
unsuccessful.
Chapter Summary