chap2
chap2
Contents
Hanoi University of Science and Technology
TRƯỜNG ĐẠI HỌCSchool
BÁCH of KHOA
Information and Communications Technology
HÀ NỘI
VIỆN CÔNG NGHỆ THÔNG TIN VÀ TRUYỀN THÔNG
1. Brute force
2. Recursion
3. Backtracking
4. Divide and conquer
Chapter 2. Algorithmic paradigms
5. Dynamic programming
Nguyễn Khánh Phương
4
Contents 1. Brute force (Exhaustive search)
Brute force – Exhaustive search:
1. Brute force • When the problem requires finding objects that satisfy a certain properties from
2. Recursion a given se of projects, we can apply the brute force:
– Browse all the objects: for each object, check whether it satisfies the
3. Backtracking required properties or not, if so, that object is the solution to find, if not,
keep searching.
4. Divide and conquer Example (Traveling Salesman Problem): A tourist wants to visit n cities T1, T2, ...,
Tn. Itinerary is a way of going from a certain city through all the remaining cities,
5. Dynamic programming each city exactly once, and then back to the starting city. Let dij the cost of going
fron Ti to Tj (i, j = 1, 2,..., n). Find itinerary with minimum total cost.
Answer: browse all n! possible itineraries, each itinerary calculates the
corresponding trip cost, and compares these n! values to get the one with minimum
value.
• Brute force: simple, but computation time is inefficient.
Example 1:
f(0) = 3, n=0
f(n+1) = 2f(n) + 3, n>0
Fractals are examples of recursively constructed Then we have: f(1) = 2 × 3 + 3 = 9, f(2) = 2 × 9 + 3 = 21, ...
images (objects that repeat recursively).
Recursive Functions factorial(3);
Example 2: Recursive definition to calculate n! int factorial(int n){
if (n==0)
f(0) = 1 return 1;
f(n) = n * f(n-1) else
return n*factorial(n-1);
}
To calculate the value of recursive function, we replace it gradually according to the
recursive definition to obtain the expression with smaller and smaller arguments until (7) Return 6 Execution of the factorial
we get the first condition. (3) function will stop
until factorial (2) returns
For example: factorial(3):
the result
recursive 3 == 0 ? NO
return factorial(2)*3 (1) Inside function factorial(3) When factorial (2)
(6) return 2 call to factorial(2) returns the result, the
factorial(2): factorial (3) function
2 == 0 ? NO continues to be executed
return factorial(1)*2 (2) Inside function factorial(2)
(5) return 1 call to factorial(1)
5! = 5 · 4! = 5 · 4 · 3! = 5 · 4 · 3 · 2! = 5 · 4 · 3 · 2 · 1! factorial(1):
= 5 · 4 · 3 · 2 · 1 · 0! = 5 · 4 · 3 · 2 · 1 · 1 = 120 1 == 0 ? NO
return factorial(0)*1 (3) Inside function factorial(1)
int factorial(int n){
call to factorial(0)
if (n==0)
(4) return 1 factorial(0):
First condition return 1;
0 == 0 ? YES
else return 1
return n*factorial(n-1);
}
factorial(4); factorial(4);
int factorial(int n){ int factorial(int n){
if (n==0) if (n==0)
factorial(4) else
return 1; factorial(4) else
return 1;
n=4
Returns 4*factorial(3)
n=4 n=4
Returns 4*factorial(3) Returns 4*factorial(3)
n=3 n=3
Returns 3*factorial(2) Returns 3*factorial(2)
n=2
Returns 2*factorial(1)
factorial(4); factorial(4);
int factorial(int n){ int factorial(int n){
if (n==0) if (n==0)
factorial(4) else
return 1; factorial(4) else
return 1;
n=4 n=4
Returns 4*factorial(3) Returns 4*factorial(3)
n=3 n=3
Returns 3*factorial(2) Returns 3*factorial(2)
n=2 n=2
Returns 2*factorial(1) Returns 2*factorial(1)
n=1 n=1
Returns 1*factorial(0) Returns 1*factorial(0)
n=0
Returns 1
factorial(4); factorial(4);
int factorial(int n){ int factorial(int n){
if (n==0) if (n==0)
factorial(4) else
return 1; factorial(4) else
return 1;
n=4 n=4
Returns 4*factorial(3) Returns 4*factorial(3)
n=3 n=3
Returns 3*factorial(2) Returns 3*factorial(2)
n=2 n=2
Returns 2*factorial(1) Returns 2*factorial(1)
n=1
1
Returns 1*factorial(0)
factorial(4); factorial(4);
int factorial(int n){ int factorial(int n){
if (n==0) if (n==0)
factorial(4) else
return 1; factorial(4) else
return 1;
n=4 n=4
Returns 4*factorial(3) Returns 4*factorial(3)
n=3
6
Returns 3*factorial(2)
2
factorial(4); Recursive functions are all re-implementable using the while / for loop
24
return n*factorial(n-1);
} int factorial (int n)
{
i=n; fact = 1;
int factorial(int n){
while(i >= 1) if (n==0)
{ return 1;
else
fact=fact*i; return n*factorial(n-1);
i--; }
}
return fact;
}
Note: Replacing a recursive function by a non-recursive function is often
called recursive reduction (khử đệ quy). Recursive reduction is not
always as easy to perform as it is in the case of factorial calculations.
helperProd(list, 1)
helperProd(list, 0)
}
} 2.5. Recursion with memorization
Computation time:
T(n) = if (base case) then const cost
else ( time to solve all subproblems +
time to combine solutions)
Computation time depends on:
– Number of subproblems
– Size of subproblem NGUYỄN KHÁNH PHƯƠNG NGUYỄN KHÁNH PHƯƠNG 44
CS - SOICT-HUST CS - SOICT-HUST
– Time to combine solutions of subproblems
Example 1: Calculate the binomial coefficient Example 2: Recursive Binary Search
• The binomial coefficient C(n,k) is defined recursively as Input: An array S consists of n elements: S[0],…,S[n-1] in ascending
following: order; Value key with the same data type as array S.
Output: the index in array if key is found, -1 if key is not found
C(n,0) = 1, C(n,n) =1; where n >=0,
Binary search algorithm: The value key either
C(n,k) = C(n-1,k-1)+C(n-1,k), where 0 < k < n equals to the element at the middle of the array S,
or is at the left half (L) of the array S,
or is at the right half (R) of the array S.
• Recursive implementation on C:
(The situation L (R) happen only when key is smaller (larger) than the element at
int C(int n, int k){ the middle of the array S)
if ((k==0)||(k==n)) return 1;
int binsearch(int low, int high, int S[], int key)
else return C(n-1,k-1)+C(n-1,k);
6 13 14 25 33 43 51 53 64 72 84 93 95 96 97 key = 33
} 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
6 13 14 25 33 43 51 53 64 72 84 93 95 96 97 6 13 14 25 33 43 51 53 64 72 84 93 95 96 97
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
lo mid hi lo hi
binsearch(0, 14, S, 33); binsearch(0, 14, S, 33);
The section to be binsearch(0, 6, S, 33); binsearch(0, 6, S, 33);
investigated is halved
49 50
after each iteration
6 13 14 25 33 43 51 53 64 72 84 93 95 96 97 6 13 14 25 33 43 51 53 64 72 84 93 95 96 97
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
lo mid hi lo hi
6 13 14 25 33 43 51 53 64 72 84 93 95 96 97 6 13 14 25 33 43 51 53 64 72 84 93 95 96 97
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
lo mid hi lo
hi
binsearch(0, 14, S, 33); binsearch(0, 14, S, 33);
The section to be binsearch(0, 6, S, 33); binsearch(0, 6, S, 33);
investigated is halved binsearch(4, 6, S, 33); binsearch(4, 6, S, 33);
53 54
after each iteration binsearch(4, 4, S, 33); binsearch(4, 4, S, 33);
(0,0) (0,C)
(i, j)
(D,0) (D,C)
NGUYỄN KHÁNH PHƯƠNG
CS - SOICT-HUST
(i, j)
• If you step over the edge of the grid (no longer on the grid), there is no path to the node
(D,0) (D,C) (D, C):
– if (i > D) OR (j > C): CountPaths(i,j, D, C) return 0
• To solve the problem CountPaths(i,j, D, C), we need to solve which
subproblems ?
– From node (i, j) there are only 2 ways: DONE ?
• Go down: to node (i+1, j) CountPaths(i+1,j, D, C) NO: because all subproblems
• Go right: to node (i, j+1) CountPaths(i,j+1, D, C) will return 0
CountPaths(i,j, D, C) = CountPaths(i+1,j, D, C)
+ CountPaths(i,j+1, D, C) One more special case should be considered: when you are at line D or column C
there is a path (this path does not need any steps) 60
Example 5: Path on grid Example 5: Path on grid: VARIATION
• Given the grid of size D*C. • Besides going either downwards or to the right, we can also go diagonally.
• You are only allowed to move from one node to other node on the grid in • How many paths are there from node (i, j) to node (D, C).
either direction downwards or to the right. – Function: int CountPaths(int i, int j, int D, int C)
• How many paths are there from node (i, j) to node (D, C).
– Function: int CountPaths(int i, int j, int D, int C)
79
Tower a Tower c
Toán rời rạc
Tower b
Tower a Tower c Tower b
Exercise: Tower of Hanoi Exercise: Tower of Hanoi
• h1 = 1 • h1 = 1
• For n ≥ 2, we need to do 3 following steps to transfer all disks from tower (a) to tower (c): • For n ≥ 2, we need to do 3 following steps to transfer all disks from tower (a) to tower (c):
(1) Move the top n - 1 disks (following the rules of the game) from tower (a) to tower (b) (1) Move the top n - 1 disks (following the rules of the game) from tower (a) to tower (b)
The problem of n-1 disks #moves = hn-1
(2) Move the largest disk to the tower (c) (2) Move the largest disk to the tower (c)
#moves = 1
(3) Move the n-1 disks (following the rules of the game) from tower (b) to tower (c), placing (3) Move the n-1 disks (following the rules of the game) from tower (b) to tower (c), placing
them on top of the largest disk them on top of the largest disk
The problem of n-1 disks #moves = hn-1
hn = 2hn-1 + 1, n ≥ 2
h1 = 1
Tower a Tower c
Toán rời rạc
Tower b Tower a Tower c
Toán rời rạc
Tower b
Tower a Tower c
Toán rời rạc
Tower b
Tower of Hanoi: Implementation Exercise: Tower of Hanoi
• Let T(n) = 2T(n-1) + 1. What are the parameters?
a=
b= hn = 2 hn−1 + 1
d=
Therefore, which condition applies?
Example: Duplication of subproblems when calculating C(5,3) Example: Duplication of subproblems when calculating Fibonacci F(4)
int F (int n) {
if (n < 2) return n;
else return F(n-1)+F(n-2);
}
C(5,3)
F (4)
C(4,2) C(4,3)
F (3) F (2)
C(3,1) C(3,2) C(3,2) C(3,3)
F (1) F (0)
C(1,0) C(1,1) C(1,0) C(1,1) C(1,0) C(1,1)
Recursive with memorization Example: Recursive with memorization to calculate C(n,k)
• To overcome this phenomenon, the idea of recursive with memorization int C(int n,int k){
is: We will use the variable to memorize information about the solution if (D[n][k]>0) return D[n][k];
of subproblem right after the first time it is solved. This allows to shorten else{
the computation time of the algorithm, because, whenever needed, it can D[n][k] = C(n-1,k-1)+C(n-1,k);
be looked up without having to solve the subproblems that have been return D[n][k];
solved before. }
Example: Recursive algorithm calculates binomial coefficients, we put a }
variable Before calling function C(n, k), we need to initialize array D[ ][ ] as following:
• D[n][k] to record calculated value of C(n, k). • D[i][0] = 1, D[i][n]=1, where i = 0,1,..., n;
• Initially D[n][k]=0, when C(n, k) is calculated, this value will be stored in • D[i][j] = 0, for remaining values of i, j
D[n][k]. Therefore, if D[n][k]>0 then it means there is no need to
recursively call function C(n, k)
Portion A
o If you get stuck before you A B
find your way out, then you C
"backtrack" to the junction. B
stuck • If you try all choices and never found
o At this point in time you know that a way out, then there IS no solution to A
Portion A will NOT lead you out the maze.
of the maze,
o so you then start searching in
Portion B
• Elements of the set D are called feasible solution (lời giải chấp • The problem of enumerating all m-element subsets of set N = {1, 2, ...,
nhận được). n} requires to enumerate elements of the set:
S(m,n) = {(a1,..., am)Nm: 1 ≤ a1 < ... < am ≤ n }.
• The problem of enumerating all permutations of natural numbers 1, 2,
..., n requires to enumerate elements of the set
n = {(a1,..., an) Nn: ai ≠ aj ; i ≠ j }.
NGUYỄN KHÁNH PHƯƠNG
CS - SOICT-HUST
Backtracking (Thuật toán quay lui) Example 1: Enumerate all binary string of length n
3.1. Algorithm diagram • Problem to enumerate all binary string of length n leads to the
enumeration of all elements of the set:
3.2. Generate basic combinatorial configurations An = {(a1, ..., an): ai {0, 1}, i=1, 2, ..., n}.
– Generate binary strings of length n • We consider how to solve two issue keys to implement backtracking
algorithm:
– Generate m-element subsets of the set of n elements – Build candidate set Sk: We have S1 = {0, 1}. Assume we have
– Generate permutations of n elements binary string of length k-1 (a1, ..., ak-1), then Sk = {0,1}. Thus, the
candidate sets for each position of the solution are determined.
– Implement the loop to enumerate all elements of Sk: we can use the
loop for
for (y=0; y<=1; y++) in C/C++
()
Sk = {0,1} using namespace std;
int n, count;
{
for (int j = 0; j<=1; j++)
int a[100]; {
a[k] = j;
0 1 void PrintSolution() if (k == n) PrintSolution();
else Try(k+1);
{
}
(0) int i, j;
}
(1) count++;
cout<<endl;
}
(000) (001) (010) (011) (100) (101) (110) (111)
Decision tree to enumerate binary strings of length 3 Program in C++ (non recursive)
Try(1) ()
Sk = {0,1} using namespace std; {
int n, count,k; k=1; s[k]=0;
int a[100], s[100]; while (k > 0)
0 1 {
void PrintSolution() while (s[k] <= 1)
(0) {
Try(2) Try(2) (1) {
1 int i, j; a[k]=s[k];
0 1 0 count++; s[k]=s[k]+1;
(00) (01) Try(3) (10) Try(3) (11) cout<<“String # " << count<<": "; if (k==n) PrintSolution();
Try(3) Try(3)
for (i=1 ; i<= n ;i++) else
0 1 0 1 0 1 0 1
cout<<a[i]<<" "; {
k++; s[k]=0;
(000) (001) (010) (011) (100) (101) (110) (111) cout<<endl; }
} }
k--; // BackTrack
}
NGUYỄN KHÁNH PHƯƠNG
CS - SOICT-HUST
}
Program in C++ (non recursive) Backtracking (Thuật toán quay lui)
int main() { 3.1. Algorithm diagram
cout<<“Enter value of n = ";cin>>n;
count = 0; GenerateString(); 3.2. Generate basic combinatorial configurations
cout<<“Number of strings = "<<count<<endl;
– Generate binary strings of length n
}
– Generate m-element subsets of the set of n elements
– Generate permutations of n elements
Example 2. Generate m-element subsets of the set of n elements Example 2. Generate m-element subsets of the set of n elements
Problem: Enumerate all m-element subsets of the set n elements N = We consider how to solve two issue keys to implement backtracking:
{1, 2, ..., n}. • Build candidate set Sk:
With the condition: : 1 a1 < a2 < ... < am n
Example: Enumerate all 3-element subsets of the set 5 elements N = {1, 2, 3, 4, 5} we have S1 = {1, 2, ..., n-(m-1) }.
Solution: (1, 2, 3), (1, 2, 4), (1, 2, 5), (1, 3, 4), (1, 3, 5), (1, 4, 5), (2, 3, 4), (2, 3, Assume the current subset is (a1, ..., ak-1), with the condition ak-1 <
5), (2, 4, 5), (3, 4, 5) ak < . . . < am ≤ n, we have Sk = {ak-1+1, ak-1+2, ..., n-(m-k)}.
• Implement the loop to enumerate all elements of Sk: we can
use the loop for
Equivalent problem: Enumerate all elements of set: for (j=a[k-1]+1;j<=n-m+k;j++)
S(m,n)={(a1,..., am)Nm: 1 ≤ a1<...<am≤ n}
(1,2,3) (1,2,4) (1,2,5) (1,3,4) (1,3,5) (1,4,5) (2,3,4) (2,3,5) (2,4,5) (3,4,5)
procedure D-and-C(n)
begin Let T(n) be the computation time of the algorithm to solve the problem of size n
if (n ≤ n0) then • D(n): time to divide
Solve the problem directly
• C(n): time to synthesize the solutions
else
begin Then
Divide the problem into K subproblems of size n/b 𝑐 𝑤ℎ𝑒𝑛 𝑛 ≤ 𝑛
for (each subproblem in K subproblems) do D-and-C(n/b) 𝑇 𝑛 = 𝑛
𝑘𝑇 + 𝐷 𝑛 + 𝐶 𝑛 𝑤ℎ𝑒𝑛 𝑛 > 𝑛
Synthesize the solutions of K subproblems to obtain the 𝑏
solution of the problem with size n. where c is the constant
end; NGUYỄN KHÁNH PHƯƠNG135 136
Bộ môn KHMT – ĐHBK HN
end;
Example. 4. Divide and conquer
procedure D-and-C(int n)
4.1. Algorithm diagram
begin
if (n == 0) return; 4.2. Some illustrative examples
D-and-C(n/2);
– Example 1. Binary search
D-and-C(n/2);
for (int i =0 ; i < n; i++) – Example 2. Multiply integer numbers
begin
Perform operations requires constant time
end;
end;
Example 2. Multiply 2 integer numbers - Karatsuba algorithm (1962) Example 2. Multiply 2 integer numbers - Karatsuba algorithm (1962)
• We have: x = xn-1 xn-2 ... x1 x0 và y = yn-1 yn-2 ... y1 y0 We have: x = xn-1 xn-2 ... x1 x0 và y = yn-1 yn-2 ... y1 y0
• Set: Then:
• Basic case: The multiplication of two 1-digit integers can be done Karatsuba discovered how to perform 2 integer n-digit numbers
directly; multiplication requires only 3 multiplications of n/2-digit
• Divide: if n>1 then the product of 2 integers with n digits can be numbers as following:
represented through 4 products of 4 integer numbers with n/2 digits: a*c, • Set: U = ac, V = bd, W =(a+b)(c+d)
a*d, b*c, b*d
Then: ad + bc = W – U – V,
• Combine: to calculate z = xy when we already know the above 4
products, we only need to perform additions (can be done in O(n)) and And calculate:
multiply with power of 10 (can be done in O(n), by inserting an z = x*y = (a10n/2 +b) (c10n/2 +d)
appropriate number of digits 0 to the right). = (ac) 10n + (ad + bc) 10n/2 + bd
= U10n + (W-U-V)10n/2 + V.
• Combine solutions:
– From the solution of smaller subproblems, in turn, seek to construct the solution
of the problem of the larger size, until the solution of the original problem
(which is the subproblem of the largest size) is obtained.
5.1. Dynamic programming Algorithm diagram 5.1. Dynamic programming Algorithm diagram
There are many similarities with the Divide and Conquer: • The technique of solving subproblems of dynamic programming is
• Divide and conquer: a bottom-up process: small-sized subproblems are solved first,
– Divide the given problem into independent subproblems then use the solution of these subproblems to build the solution of
– Solve each subproblem (by recursion) the larger problem;
– Combine solutions of subproblems into solutions of given problems. • Divide and conquer method: big problems are broken down into
• Dynamic programming: subproblems, and subproblems are treated recursively (top-down).
a1, a2, … , an
5.2. Some illustrative examples
The contiguous subarray ai, ai+1 , …, aj with 1 ≤ i ≤ j ≤ n is a subarray of
– Example 1. Maximum subarray problem the given array and ∑ 𝑎 is called as the value of this subarray
– Example 2. Longest common subsequence The task is to find the maximum value of all possible subarrays, in other
words, find the maximum ∑ 𝑎 . The subarray with the maximum value is
called as the maximum subarray.
Example: Given the array -2, 11, -4, 13, -5, 2 then the maximum subarray is
11, -4, 13 with the value = 11+ (-4)+13 =20
157 158
Example 1: The maximum subarray problem Dynamic programming for maximum subarray problem
Let max_sum(i) be the value of the maximum subarray of array a1, a2, ..., ai , and
this subarray ends at ai (this subarray includes ai)
159 NGUYỄN KHÁNH PHƯƠNG 160
CS - SOICT-HUST
Dynamic programming for maximum subarray problem Dynamic programming for maximum subarray problem
Step 1. Find dynamic programming formula Step 2. Implement dynamic programming formula
• Let max_sum(i) the value of maximum subarray of array a1, a2, ...,
ai , i = 1, .., n and this subarray ends at ai (this subarray includes ai)
• Basic case: max_sum(1) = a[1]
• Find recursive formula for max_sum(i)
max_sum(i)= max(a[i], a[i] + max_sum(i-1))
Dynamic programming for maximum subarray problem The maximum subarray problem
In the main function, we need to call max_sum(n); this function will
calculate all values max_sum(i) for all 1 ≤ i ≤ n
Then, solution to the problem is the maximum of the max_sum(i) values
already stored in array mem[i]
LCS = “aninn”
Complexity ?
O(m*n)
When call lcs(len(a)-1, len(b)-1): there are m*n input possibilities for
function
For each input:
• Either the result is taken directly from table if it has been calculated before: O(1) if assume
taking a value from memory just requires O(1)
• Or need to calculate the result and then store it: O(1) if assume each recursive call and
function max, summing only take a constant amount of time
Each input is called up to 1 time
173
Total time is O(m*n)