0% found this document useful (0 votes)
41 views16 pages

Chapter 5 Divide and Conquer Student

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)
41 views16 pages

Chapter 5 Divide and Conquer Student

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
You are on page 1/ 16

1

Chapter 5: Divide-and-Conquer

Introduction

Divide-and-conquer is probably the best known general algorithm design technique.


Divide-and-conquer algorithms work according to the following general plan:
Step 1. A problem is divided into several subproblems of the same type, ideally of
about equal size.
Step 2. The subproblems are solved (typically recursively, though sometimes a
different algorithm is employed, especially when subproblems become small
enough).
Step 3. The solutions to the subproblems are combined to get a solution to the original
problem.

Example: Finding the maximum value from an array of 𝑛 numbers (for simplicity,
𝑛 is a power of 2).

The general divide-and-conquer recurrence

In the most typical case of divide-and-conquer, a problem’s instance of size 𝑛 is


divided into 𝑎(> 1) instances of size 𝑛⁄𝑏 where 𝑏 > 1. For simplicity, assuming that size
𝑛 is a power of 𝑏; we get the following recurrence for the running time 𝑇(𝑛):

𝑇(𝑛) = 𝑎𝑇(𝑛⁄𝑏) + 𝑓(𝑛)

where 𝑓(𝑛) is a function that accounts for the time spent on dividing an instance of size 𝑛
into instances of size 𝑛⁄𝑏 and combining their solutions. This recurrence is called the
general divide-and-conquer recurrence.

The efficiency analysis of many divide-and-conquer algorithms is greatly simplified


by the following theorem:
Master theorem: Given the divide-and-conquer recurrence 𝑇(𝑛) = 𝑎𝑇(𝑛⁄𝑏) + 𝑓(𝑛). If
𝑓(𝑛) ∈ Θ(𝑛𝑑 ) where 𝑑 ≥ 0 then:
Θ(𝑛𝑑 ) 𝑎 < 𝑏𝑑
𝑇(𝑛) ∈ {Θ(𝑛𝑑 log 𝑛) 𝑎 = 𝑏 𝑑
Θ(𝑛log𝑏 𝑎 ) 𝑎 > 𝑏 𝑑
Analogous results hold for the Ο and  notations, too.
2

Example: Finding the maximum value from an array of 𝑛 numbers (for simplicity,
𝑘
𝑛 = 2 ).
findMax(a, l, r) {
if (l == r) return a[l];
m = (l + r) / 2;
return max(findMax(a, l, m), findMax(a, m + 1, r));
}
The divide-and-conquer recurrence is as follows:
𝑛
2𝑇 ( ) + Θ(1) 𝑛 > 1
𝑇(𝑛) = { 2
0 𝑛=1

Example: Finding simultaneously the maximum and minimum values from an array
of 𝑛 numbers.

Algorithm
MinMax(l, r, & min, & max) {
if (l ≥ r - 1)
if (a[l] < a[r]) {
min = a[l];
max = a[r];
}
else {
min = a[r];
max = a[l];
}
else {
m = (l + r) / 2;
MinMax(l, m, minL, maxL);
MinMax(m + 1, r, minR, maxR);
min = (minL < minR) ? minL : minR;
max = (maxL < maxR) ? maxR : maxL;
}
}

The divide-and-conquer recurrence is as follows:


𝑛 𝑛
𝐶 (⌊ ⌋) + 𝐶 (𝑛 − ⌊ ⌋) + 2 𝑛>2
𝐶(𝑛) = { 2 2
1 𝑛≤2
3

Mergesort

This approach sorts a given array 𝑎1 , 𝑎2 , … , 𝑎𝑛 by dividing it into two halves:


𝑎1 , 𝑎2 , … , 𝑎⌊𝑛⌋
2
𝑎⌊𝑛⌋+1 , 𝑎⌊𝑛⌋+2 , … , 𝑎𝑛
2 2
sorting each of them recursively, and then merging the two smaller sorted arrays into a
single sorted one.

32749168

3274 9168
Tách

32 74 91 68

3 2 7 4 9 1 6 8

23 47 19 68
Trộn

2347 1689

12346789

Algorithm
mergeSort(a[1 .. n], low, high) {
if (low < high) {
mid = (low + high) / 2;
mergeSort(a, low, mid);
mergeSort(a, mid + 1, high);
merge(a, low, mid, high);
}
}
4

merge(a[1 .. n], low, mid, high) {


i = low;
j = mid + 1;
k = low;
while (i  mid) && (j  high)
if (a[i]  a[j])
buf[k ++] = a[i ++];
else
buf[k ++] = a[j ++];

if (i > mid)
buf[k .. high] = a[j .. high];
else
buf[k .. high] = a[i .. mid];

a[low .. high] = buf[low .. high];


}
mergeSort(a, 1, n);

How efficient is mergesort?

• Assuming that the key comparison is the basic operation:


In the best case:
𝑛 𝑛 𝑛
𝑇 (⌊ ⌋) + 𝑇 (⌈ ⌉) + ⌊ ⌋ 𝑛>1
𝑇(𝑛) = { 2 2 2
0 𝑛=1
Hint: 𝑇(𝑛) ∈ Θ(𝑛 log 𝑛)

In the worst case:


𝑛 𝑛
𝑇 (𝑛 ) = {
𝑇 (⌊ ⌋) + 𝑇 (⌈ ⌉) + (𝑛 − 1) 𝑛 > 1
2 2
0 𝑛=1
Hint: 𝑇(𝑛) ∈ Θ(𝑛 log 𝑛)
• Assuming that the assignment statement is the basic operation:
𝑛 𝑛
𝑀 (⌊ ⌋) + 𝑀 (⌈ ⌉) + 𝑛 𝑛 > 1
𝑀(𝑛) = { 2 2
0 𝑛=1
Hint: 𝑀(𝑛) ∈ Θ(𝑛 log 𝑛)
5

Quicksort

Unlike mergesort, which divides its input elements according to their position in the
array, quicksort divides them according to their value. This process is called partition.
A partition is an arrangement of the array’s elements so that all the elements to the
left of some element 𝑎𝑠 are less than or equal to 𝑎𝑠 , and all the elements to the right of 𝑎𝑠
are greater than or equal to it:
{𝑎1 … 𝑎𝑠−1 } ≤ 𝑎𝑠 ≤ {𝑎𝑠+1 … 𝑎𝑛 }
After a partition is achieved, 𝑎𝑠 will be in its final position in the sorted array, and
we can continue sorting the two subarrays to the left and to the right of 𝑎𝑠 independently
by the same method.

Algorithm
Quicksort(a[left .. right]) {
if (left < right){
s = Partition(a[left .. right]);
Quicksort(a[left .. s – 1]);
Quicksort(a[s + 1 .. right]);
}
}
Partition(a[left .. right]) {
p = a[left];
i = left;
j = right + 1;
do {
do i++; while (a[i] < p);
do j--; while (a[j] > p);
swap(a[i], a[j]);
} while (i < j);

swap(a[i], a[j]);
swap(a[left], a[j]);

return j;
}

Does this design work?


6

Analysis of Quicksort

For simplicity, assuming that the sequence 𝑎1 , 𝑎2 , … , 𝑎𝑛 contains no duplicate


values and the size 𝑛 is a power of 2: 𝑛 = 2𝑘 . Two comparisons in loops are the basic
operation.

In the best case:


𝐶𝑏 (𝑛) ∈ Θ(𝑛 log 𝑛)

In the worst case:


𝐶𝑤 (𝑛) ∈ Θ(𝑛2 )

In the average case:


𝐶(𝑛) ≈ 1.39𝑛 log 2 𝑛
7

Multiplication of Large Integers

A simple quadratic-time algorithm for multiplying large integers is one that mimics
the standard way learned in school. We will develop one that is better than quadratic time.

The basic idea: Observing the multiplication of two complex numbers


(𝑎 + 𝑏𝑖)(𝑐 + 𝑑𝑖) = (𝑎𝑐 − 𝑏𝑑) + (𝑏𝑐 + 𝑎𝑑)𝑖
K. F. Gauss perceived that:
𝑏𝑐 + 𝑎𝑑 = (𝑎 + 𝑏)(𝑐 + 𝑑) − (𝑎𝑐 + 𝑏𝑑)

We assume that the data type large_integer representing a large integer was
constructed. It is not difficult to write linear-time algorithms for three operations:
mul 10m, div 10m, and mod 10m.
Let’s consider the algorithm that implements the multiplication of two large
integers: 𝑢 × 𝑣
Algorithm
large_integer MUL(large_integer u, v) {
large_integer x, y, w, z;

n = max(number of digits in u, number of digits in v);


if (u == 0 || v == 0)
return 0;
else
if (n  )
return u × v; // built-in operator
else {
m = n / 2;
x = u div 10m; y = u mod 10m;
w = v div 10m; z = v mod 10m;
return MUL(x, w) mul 102m +
(MUL(x, z) + MUL(y, w)) mul 10m +
MUL(y, z);
}
}
8

Analysis of the algorithm


The divide-and-conquer recurrence is as follows:
𝑛
4𝑇 ( ) + Θ(𝑛) 𝑛 > 𝛼
𝑇(𝑛) = { 2
1 𝑛≤𝛼
The Master theorem implies that 𝑇(𝑛) ∈ Θ(𝑛2 ).

Algorithm (upgraded version)


large_integer MUL(large_integer u, v, n) {
n = max(number of digits in u, number of digits in v);

if (u == 0 || v == 0)
return 0;
else
if (n  )
return u × v;
else {
m = n / 2;
x = u div 10m; y = u mod 10m;
w = v div 10m; z = v mod 10m;
r = MUL(x + y, w + z);
p = MUL(x, w);
q = MUL(y, z);

return p mul 102m + (r – p – q) mul 10m + q;


}
}
In this case, the divide-and-conquer recurrence is as follows:
𝑛
3𝑇 ( ) + Θ(𝑛) 𝑛 > 𝛼
𝑇(𝑛) = { 2
1 𝑛≤𝛼

Since 𝑑 = 1, 𝑎 = 3, 𝑏 = 2, the Master theorem implies that 𝑇(𝑛) ∈ Θ(𝑛log2 3 ) ≈


Θ(𝑛1.585 ).
9

Extension: Multiplication of two positive integers of 𝑛 bits. Assuming that 𝑛 is the power
of 2.

Let’s 𝑥 and 𝑦 be two positive integers of 𝑛 bits. Obviously:


𝑥 = 2𝑛/2 𝑥𝐿 + 𝑥𝑅
𝑦 = 2𝑛/2 𝑦𝐿 + 𝑦𝑅
where 𝑥𝐿 , 𝑥𝑅 are two positive integers represented by 𝑛⁄2 leftmost bits and 𝑛⁄2 rightmost
bits of 𝑥 , respectively; similarly, 𝑦𝐿 , 𝑦𝑅 are two positive integers represented by 𝑛⁄2
leftmost bits and 𝑛⁄2 rightmost bits of 𝑦, respectively.

Example: Given 𝑥 = 13510 = 100001112. Then,


𝑥𝐿 = 810 (= 10002 )
𝑥𝑅 = 710 (= 01112 )
𝑥 = 2𝑛/2 𝑥𝐿 + 𝑥𝑅 = 28/2 × 810 + 710

Now, we get:
𝑥 × 𝑦 = (2𝑛/2 𝑥𝐿 + 𝑥𝑅 ) × (2𝑛/2 𝑦𝐿 + 𝑦𝑅 ) = 2𝑛 × 𝑥𝐿 𝑦𝐿 + 2𝑛/2 × (𝑥𝐿 𝑦𝑅 + 𝑥𝑅 𝑦𝐿 ) + 𝑥𝑅 𝑦𝑅
Algorithm
int multiply(x, y) {
n = max(|x| , |y| );
bit bit

if (n  ) return x × y;
xL = n / 2 leftmost bits of x;
xR = n / 2 rightmost bits of x;
yL = n / 2 leftmost bits of y;
yR = n / 2 rightmost bits of y;

r = multiply(xL + xR, yL + yR);


p = multiply(xL, yL);
q = multiply(xR, yR);

return p × 2n + (r - p - q) × 2n/2 + q;
}
10

Strassen’s Matrix Multiplication

Suppose we want the product 𝐶 of two 2 × 2 matrices, 𝐴 and 𝐵. That is,


𝑐11 𝑐12 𝑎11 𝑎12 𝑏11 𝑏12
[𝑐
21 𝑐22 ] = [𝑎21 𝑎22 ] × [𝑏21 𝑏22 ]
𝑎 × 𝑏11 + 𝑎12 × 𝑏21 𝑎11 × 𝑏12 + 𝑎12 × 𝑏22
= [ 11 ]
𝑎21 × 𝑏11 + 𝑎22 × 𝑏21 𝑎21 × 𝑏12 + 𝑎22 × 𝑏22
Of course, the time complexity of this straightforward method is 𝑇(𝑛) = 𝑛3, where
𝑛 is the number of rows and columns in the matrices. To be specific, the above matrix
multiplication requires eight multiplications and four additions.
However, Strassen determined that if we let
𝑚1 = (𝑎11 + 𝑎22 ) × (𝑏11 + 𝑏22 )
𝑚2 = (𝑎21 + 𝑎22 ) × 𝑏11
𝑚3 = 𝑎11 × (𝑏12 − 𝑏22 )
𝑚4 = 𝑎22 × (𝑏21 − 𝑏11 )
𝑚5 = (𝑎11 + 𝑎12 ) × 𝑏22
𝑚6 = (𝑎21 − 𝑎11 ) × (𝑏11 + 𝑏12 )
𝑚7 = (𝑎12 − 𝑎22 ) × (𝑏21 + 𝑏22 )
the product 𝐶 is given by
𝑐11 𝑐12 𝑚1 + 𝑚4 − 𝑚5 + 𝑚7 𝑚3 + 𝑚5
[𝑐
21 𝑐22 ] = [ 𝑚2 + 𝑚4 𝑚1 + 𝑚3 − 𝑚2 + 𝑚6 ]
Strassen’s method requires seven multiplications and 18 additions/subtractions.
Thus, we have saved ourselves one multiplication at the expense of doing 14 additional
additions or subtractions.
Let 𝐴 and 𝐵 be matrices of size 𝑛 × 𝑛, where 𝑛 = 2𝑘 . Let 𝐶 be the product of 𝐴 and
𝐵. Each of these matrices is divided into four submatrices as follows:
𝐶 𝐶12 𝐴 𝐴12 𝐵 𝐵12
[ 11 ] = [ 11 ] × [ 11 ]
𝐶21 𝐶22 𝐴21 𝐴22 𝐵21 𝐵22
where
𝑐1,1 ⋯ 𝑐1,𝑛 𝑐1,𝑛+1 ⋯ 𝑐1,𝑛
2 2
𝐶11 =[ ⋮ ⋱ ⋮ ] 𝐶12 = [ ⋮ ⋱ ⋮ ]
𝑐𝑛,1 ⋯ 𝑐𝑛,𝑛 𝑐𝑛,𝑛+1 ⋯ 𝑐𝑛,𝑛
2 22 22 2
𝑐𝑛+1,1 ⋯ 𝑐𝑛+1,𝑛 𝑐𝑛+1,𝑛+1 ⋯ 𝑐𝑛+1,𝑛
2 2 2 2 2 2
𝐶21 =[ ⋮ ⋱ ⋮ ] 𝐶22 = [ ⋮ ⋱ ⋮ ]
𝑐𝑛,1 ⋯ 𝑐𝑛,𝑛 𝑐𝑛,𝑛+1 ⋯ 𝑐𝑛,𝑛
2 2
Using Strassen’s method, first we compute:
11

𝑀1 = (𝐴11 + 𝐴22 ) × (𝐵11 + 𝐵22 )


where our operations are now matrix addition and multiplication. In the same way, we
compute 𝑀2 through 𝑀7 . Next we compute
𝐶11 = 𝑀1 + 𝑀4 − 𝑀5 + 𝑀7
and 𝐶12 , 𝐶21 , 𝐶22 . Finally, the product 𝐶 of 𝐴 and 𝐵 is obtained by combining the four
submatrices 𝐶𝑖𝑗 .
Algorithm
Strassen(n, A[1..n][1..n], B[1..n][1..n], C[1..n][1..n]) {
if (n  )
C = A × B;
else {
"Partition A into 4 submatrices A11, A12, A21, A22";
"Partition B into 4 submatrices B11, B12, B21, B22";

Strassen(n/2, A11 + A22, B11 + B22, M1);



Strassen(n/2, A12 – A22, B21 + B22, M7);

C11 = M1 + M4 – M5 + M7;
C12 = M3 + M5;
C21 = M2 + M4;
C22 = M1 + M3 – M2 + M6;

Combine C11, C12, C21, C22 into C;


}
}

Analysis of the algorithm


The divide-and-conquer recurrence is as follows:
𝑛 𝑛 2
𝑇(𝑛) = {7𝑇 ( ) + 18 ( ) 𝑛>𝛼
2 2
1 𝑛≤𝛼
The Master theorem implies that: 𝑇(𝑛) ∈ Θ(𝑛log2 7 ) ≈ Θ(𝑛2.81 )
12

Find the substring with largest sum of elements in an array

 
Algorithm

sumMax(a[1..n], l, r) {
if (l == r) return max(a[l], 0);

c = (l + r) / 2;
maxLS = sumMax(a, l, c);
maxRS = sumMax(a, c + 1, r);

tmp = maxLpartS = 0;
for (i = c; i  l; i--) {
tmp += a[i];
if (tmp > maxLpartS) maxLpartS = tmp;
}
tmp = maxRpartS = 0;
for (i = c + 1; i  r; i++) {
tmp += a[i];
if (tmp > maxRpartS) maxRpartS = tmp;
}
tmp = maxLpartS + maxRpartS;
return max(tmp, maxLS, maxRS);
}
max = sumMax(a, 1, n);

Analysis of the algorithm


The divide-and-conquer recurrence is as follows:
𝑛 𝑛
𝑇 (⌊ ⌋) + 𝑇 (⌈ ⌉) + Θ(𝑛) 𝑛 > 1
𝑇(𝑛) = { 2 2
0 𝑛=1
Hint: 𝑇(𝑛) ∈ Θ(𝑛 log 𝑛)
13

Closest-Pair Problem
Let 𝑃 be a list of 𝑛 > 1 points in the Cartesian plane: 𝑃 = {𝑝1 , 𝑝2 , … , 𝑝𝑛 }. Find
a pair of points with the smallest distance between them.
For the sake of simplicity and without loss of generality, we can assume that the
points in 𝑃 are ordered in nondecreasing order of their 𝑥 coordinate. In addition, let 𝑄 be a
list of all and only points in 𝑃 sorted in nondecreasing order of the 𝑦 coordinate.
If 2 ≤ 𝑛 ≤ 3, the problem can be solved by the obvious brute-force algorithm.
Besides, 𝑛 = 2,3 is also the stopping condition of the recursive process.
𝑛 𝑛
If 𝑛 > 3, we can divide the points into two subsets 𝑃𝐿 and 𝑃𝑅 of ⌈2 ⌉ and ⌊2 ⌋ points,
respectively, by drawing a vertical line through the median of their 𝑥 coordinates so that
𝑛 𝑛
⌈2 ⌉ points lie to the left of or on the line itself, and ⌊2 ⌋ points lie to the right of or on the
line . Then we can solve the closest-pair problem recursively for subsets 𝑃𝐿 and 𝑃𝑅 . Let
𝛿𝐿 and 𝛿𝑅 be the smallest distances between pairs of points in 𝑃𝐿 and 𝑃𝑅 , respectively, and
let 𝛿 = min{𝛿𝐿 , 𝛿𝑅 }.

S 

R

L

 

Note that 𝛿 is not necessarily the smallest distance between all the point pairs
because points of a closer pair can lie on the opposite sides of the separating line .
Therefore, we need to examine such points. Obviously, we can limit our attention to the
points inside the symmetric vertical strip of width 2𝛿 around the separating line , since
the distance between any other pair of points is at least 𝛿.
Algorithm
14

ClosestPair(Point P[1..n], Point Q[1..n]) {


if (|P|  3)
return the minimal distance found by the brute-force
algorithm;

= P[n/2].x;

Copy the first n/2 points of P to PL;


Copy the same n/2 points from Q to QL;
Copy the remaining n/2 points of P to PR;
Copy the same n/2 points from Q to QR;

L = ClosestPair(PL, QL);
R = ClosestPair(PR, QR);
 = min(L, R);

Copy all the points p of Q for which |p.x - | <  into


S[1..k];
min = ;
for (i = 1; i < k; i++) {
j = i + 1;
while (j  k) && (|S[i].y – S[j].y| < min) {
min = min(√(𝑆[𝑖]. 𝑥 − 𝑆[𝑗]. 𝑥)2 + (𝑆[𝑖]. 𝑦 − 𝑆[𝑗]. 𝑦)2, min)
j++;
}
}

return min;
}

Analysis of the algorithm


The divide-and-conquer recurrence is as follows:
𝑛
𝑇(𝑛) = 2𝑇 ( ) + Θ(𝑛) ∈ Θ(𝑛 log 𝑛)
2
15

The change-making problem

Given 𝑘 denominations: 𝑑1 < 𝑑2 < ⋯ < 𝑑𝑘 where 𝑑1 = 1 . Find the minimum


number of coins (of certain denominations) that add up to a given amount of money 𝑛.

Algorithm
moneyChange(d[1..k], money) {
for (i = 1; i  k; i++)
if (d[i] == money)
return 1;

minCoins = money;
for (i = 1; i  money / 2; i++) {
tmpSum = moneyChange(d, i) + moneyChange(d, money - i);
if (tmpSum < minCoins)
minCoins = tmpSum;
}
return minCoins;
}

Analysis of the algorithm


The divide-and-conquer recurrence is as follows:
⌊𝑛⁄2⌋
𝑛
∑ (𝑇(𝑖) + 𝑇(𝑛 − 𝑖)) + Θ ( ) 𝑛 > 2
𝑇(𝑛) = 2
𝑖=1
1 𝑛=2
{ 0 𝑛=1
Hint: 𝑇(𝑛) ∈ Ω(2𝑛 )
16

Algorithm (upgraded version)


moneyChange(d[1..k], money) {
for (i = 1; i  k; i++)
if (d[i] == money)
return 1;

minCoins = money;
for (i = 1; i  k; i++)
if (money > d[i]) {
tmpSum = 1 + moneyChange(d, money - d[i]);
if (tmpSum < minCoins)
minCoins = tmpSum;
}
return minCoins;
}

You might also like