Chapter 3 - Brute Force - Student-Đã G P
Chapter 3 - Brute Force - Student-Đã G P
Chapter 3 - Brute Force - Student-Đã G P
Introduction
The subject of this chapter is brute force and its important special case, exhaustive
search. Brute force can be described as follows:
Brute force is a straightforward approach to solving a problem, usually directly
based on the problem statement and definitions of the concepts involved.
Example: Computing 𝑥 𝑛 .
By the definition of exponentiation,
𝑥𝑛 = ⏟ 𝑥 × …× 𝑥
𝑛 𝑡𝑖𝑚𝑒𝑠
𝑛
This suggests simply computing 𝑥 by multiplying 1 by 𝑥 𝑛 times.
MaxContSubSum(a[1 .. n]) {
maxSum = 0;
for (i = 1; i ≤ n; i++) {
curSum = 0;
for (j = i; j ≤ n; j++) {
curSum += a[j];
if (curSum > maxSum)
maxSum = curSum;
}
}
return maxSum;
}
MaxContSubSum(a[1 .. n]) {
maxSum = curSum = 0;
for (j = 1; j ≤ n; j++) {
curSum += a[j];
if (curSum > maxSum)
maxSum = curSum;
else
if (curSum < 0)
curSum = 0;
}
return maxSum;
}
Closest-Pair Problem
This problem calls for finding the two closest points in a set of 𝑛 points in the plan.
BruteForceClosestPoints(P[1 .. n]) {
dmin = ;
for (i = 1; i ≤ n - 1; i++)
for (j = i + 1; j ≤ n; j++) {
d = √(𝑃[𝑖]. 𝑥 − 𝑃[𝑗]. 𝑥)2 + (𝑃[𝑖]. 𝑦 − 𝑃[𝑗]. 𝑦)2 ;
if (d < dmin) {
dmin = d;
point1 = P[i];
point2 = P[j];
}
}
return <point1, point2>;
}
5
Convex-hull problem
Intuitively, the convex-hull of a set of 𝑛 points in the plane is the smallest convex
polygon that contains all of them either inside or on its boundary.
Note: Mathematicians call the vertices of such a polygon “extreme points.” Finding these
points is the way to construct the convex hull for a given set of 𝑛 points.
It’s known that the straight line through two points (𝑥1 , 𝑦1 ) and (𝑥2 , 𝑦2 ) in the
coordinate plane can be defined by the equation
𝑎𝑥 + 𝑏𝑦 = 𝑐
where 𝑎 = 𝑦2 − 𝑦1 , 𝑏 = 𝑥1 − 𝑥2 , 𝑐 = 𝑥1 𝑦2 − 𝑥2 𝑦1 .
Second, such a line divides the plane into two half-planes: for all the points in one
of them, 𝑎𝑥 + 𝑏𝑦 > 𝑐, while for all the points in the other, 𝑎𝑥 + 𝑏𝑦 < 𝑐. For the points on
the line itself, 𝑎𝑥 + 𝑏𝑦 = 𝑐.
Now, let’s consider another brute force approach whose output is a list of the
extreme points in a counterclockwise order.
Initially, the point with the lowest y-value must be the first extreme point in the
result list. If there are two points with the same lowest y-value then the leftmost one is the
first extreme point.
Assume that 𝑝 is the current extreme point, how to find out the next one?
Let’s denote 𝑐𝑢𝑟𝐴𝑛𝑔𝑙𝑒 the current angle (initially, 𝑐𝑢𝑟𝐴𝑛𝑔𝑙𝑒 = 0). The next
extreme point 𝑞 must satisfy the following condition:
̂
𝑐𝑢𝑟𝐴𝑛𝑔𝑙𝑒 ≤ (𝑝𝑞
̅̅̅, ̂
0𝑥) < (𝑝𝑟
̅̅̅, 0𝑥), ∀𝑟 ∈ 𝑆: 𝑟 ≠ 𝑝, 𝑟 ≠ 𝑞
Algorithm
computeConvexHull(S) {
convexHull = ;
// Let “first” be the first extreme point
convexHull = first;
curAngle = 0;
point cur = first;
while (true) {
[next, curAngle] = findNextExtremePoint(S, cur,
curAngle);
if (first == next)
break;
convexHull = next;
cur = next;
}
return convexHull;
}
What is the efficiency of this algorithm? In the worst case, the running time is
2 ).
Θ(𝑛 Otherwise?
8
Exhaustive Search
Let’s denote 𝑇(𝑛) the number of times the basic operation must be executed to
generate all permutations of a set of 𝑛 elements. The recurrence relation is as follows:
𝑇(𝑛) = 𝑛𝑇(𝑛 − 1) + Θ(𝑛)
with the initial condition 𝑇(1) = 0.
Hint: 𝑇(𝑛) ∈ Ω(𝑛!)
9
The problem asks to find the shortest tour through a given set of 𝑛 cities that visits
each city exactly once before returning to the city where it started.
Note: The problem can also be stated as the problem of finding the shortest Hamiltonian
circuit of a weighted graph.
In words, a Hamiltonian circuit is defined as a cycle that passes through all the
vertices of the graph exactly once.
Idea: With no loss of generality, we can assume that all circuits start and end at one
particular vertex 𝑣𝑖0 . Thus, we can get (𝑛 − 1)! potential tours by generating all the
permutations of 𝑛 − 1 intermediate cities and attach 𝑣𝑖0 to the beginning and the end of
each permutation. Then, we verify if each potential tours is a Hamiltonian circuit? If it’s
true then we compute the tour lengths, and find the shortest among them.
The sum of subsets problem consists of finding all subsets of a given set A =
{𝑎1 , 𝑎2 , … , 𝑎𝑛 } of 𝑛 distinct positive integers that sum to a positive integer 𝑘.
Example: If A = {3,5,6,7,8,9,10} and 𝑘 = 15, there will be more than one subset
whose sum is 15. The subsets are {3,5,7}, {7,8}, {6,9}, …
A brute force approach is to generate all subsets of the given set A and compute the
sum of each subset.
The time efficiency of this algoritm is Θ(2𝑛 ).
10
Knapsach problem
𝑚𝑎𝑥𝑖𝑚𝑖𝑧𝑒 ∑ 𝑣𝑖 𝑥𝑖
𝑖=1
𝑛
Assignment problem
There are 𝑛 people who need to be assigned to execute 𝑛 jobs, one person per job.
The cost if the 𝑖 𝑡ℎ person is assigned to the 𝑗 𝑡ℎ job is a known quantity 𝐶𝑖,𝑗 for each pair
𝑖, 𝑗 = 1,2, … , 𝑛. The problem is to find an assignment with the minimum total cost.
Example:
𝐶 Job 1 Job 2 Job 3 Job 4
Person 1 9 2 7 8
Person 2 6 4 3 7
Person 3 5 8 1 8
Person 4 7 6 9 4
The exhaustive-search approach to this problem would require generating all the
permutations of integers 1,2, … , 𝑛, computing the total cost of each assignment by
summing up the corresponding elements of the cost matrix, and finally selecting the one
with the smallest sum.
Chapter 4: Backtracking
Introduction
(a) (h)
1,1 1,2
(e) (j)
3,2 3,1
(c) (g)
(k)
4,3
(f)
3
Algorithm
promising(i) {
j = 1; flag = true;
while (j < i && flag) {
if (col[i] == col[j] || abs(col[i] - col[j]) == i - j)
flag = false;
j++;
}
return flag;
}
Version 1
n_Queens(i) {
if (promising(i))
if (i == n)
print(col[1 .. n]);
else
for (j = 1; j ≤ n; j++) {
col[i + 1] = j;
n_Queens(i + 1);
}
}
n_Queens(0);
Version 2
n_Queens(i) {
for (j = 1; j ≤ n; j++) {
col[i] = j;
if (promising(i))
if (i == n)
print(col[1 .. n]);
else
n_Queens(i + 1);
}
}
n_Queens(1);
4
A knight is placed on the first cell 〈𝑟0 , 𝑐0 〉 of an empty board of the size 𝑛 × 𝑛 and,
moving according to the rules of chess, must visit each cell exactly once.
4 3 -2
5 2 -1
0
6 1 1
7 0 2
-2 -1 0 1 2
Algorithm
KnightTour(i, r, c) {
for (k = 1; k ≤ 8; k++) {
u = r + row[k];
v = c + col[k];
if (i == n2)
print(h);
else
KnightTour(i + 1, u, v);
cb[u][v] = 0;
}
}
}
cb[r0][c0] = 1;
KnightTour(2, r0, c0);
5
Maze problem
# S # # . # North
# . . . . #
. # . # . #
. . . . # # West East
. . # # . G
# . . . . #
# # # . # # South
(a)
# S # # . # # S # # . #
# # # . . #
. # . # . # . # # . #
. . . . # # . . # #
. . # # . G . # # G
# . . . . # # #
# # # . # # # # # . # #
(b) (c)
1 2 3 4 5 6
#### #### #### #### #### ####
##..# ##..# ##..# ##..# ##..# ##..#
##..# ##..# ##..# ##..# ##..# ##..#
##.# ##.# ##.# ##.# ##.# #.#.#
###... ###... ###... ###... ###... ###...
G...## G...## G...## G...## G...## G...##
6
Algorithm
bool Find_Path(r, c) {
if ((r, c) Maze)
return false;
if (Maze[r][c] == ‘G’)
return true;
if (Maze[r][c] == ‘’)
return false;
if (Maze[r][c] == ‘#’)
return false;
Maze[r][c] = ‘’;
if (Find_Path(r - 1, c) == true)
return true;
if (Find_Path(r, c + 1) == true)
return true;
if (Find_Path(r + 1, c) == true)
return true;
if (Find_Path(r, c - 1) == true)
return true;
Maze[r][c] = ‘.’;
return false;
}
Find_Path(r0, c0);
7
Algorithm
bool promising(int pos, int v) {
if (pos == n && G[v][path[1]] == 0) // (3)
return false;
else
if (G[path[pos - 1]][v] == 0) // (2)
return false;
else
for (int i = 1; i < pos; i++) // (4)
if (path[i] == v)
return false;
return true;
}
Hamiltonian(bool G[1..n][1..n], int path[1..n], int pos) {
if (pos == n + 1)
print(path);
else
for (v = 1; v n; v++)
if (promising(pos, v)) {
path[pos] = v;
Hamiltonian(G, path, pos + 1);
}
}
path[1 .. n] = -1;
path[1] = 1;
Hamiltonian(G, path, 2);
8
Note: It is convenient to sort the set’s elements in increasing order. So, we will assume that
𝑤1 < 𝑤2 < ⋯ < 𝑤𝑛
The solution 𝑆 is a vector of the size 𝑛: {𝑠1 , 𝑠2 , … , 𝑠𝑛 } where 𝑠𝑖 ∈ {0,1}. For each
𝑖 ∈ {1,2, … , 𝑛}, the value of 𝑠𝑖 indicates whether 𝑤𝑖 is in the subset or not.
Level 0 0
+3 +0
Level 1 3 0
+5 +0 +5 +0
Level 2 8 3 5 0
+6 +0 +6 +0 +6 +0
Level 3 14 8 9 3 11 5
+7
+0
Level 4 15 8
✓
Algorithm
bool s[1 .. n] = {false};
total = ∑𝑛𝑖=1 𝑤[𝑖];
sort(w);
if (w[1] t total)
SoS(1, 0, total, w, s);
…
9
Assume that initially the given set has 4 items W = {𝑤1 , 𝑤2 , 𝑤3 , 𝑤4 }. The state-space
tree will be constructed as follows:
0
+ w1 + w4
+ w2 + w3
1 2 3 4
+ w2 + w4 + w3 + w4 + w4
+ w3
5 6 7 8 9 10
+ w3 + w4 + w4 + w4
11 12 13 14
+ w4
15
10
Algorithm
SoS(s[1 .. n], size, sum, start) {
if (sum == t)
print(s, size);
else
for (i = start; i n; i++) {
s[size] = w[i];
SoS(s, size + 1, sum + w[i], i + 1);
}
}
s[1 .. n] = {0};
total = ∑𝑛𝑖=1 𝑤[𝑖];
if (min(w) t && t total)
SoS(s, 1, 0, 1);
Chapter 5: Divide-and-Conquer
Introduction
Example: Finding the maximum value from an array of 𝑛 numbers (for simplicity,
𝑛 is a power of 2).
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.
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;
}
}
Mergesort
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
if (i > mid)
buf[k .. high] = a[j .. high];
else
buf[k .. high] = a[i .. mid];
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;
}
Analysis of Quicksort
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.
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;
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);
Extension: Multiplication of two positive integers of 𝑛 bits. Assuming that 𝑛 is the power
of 2.
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;
return p × 2n + (r - p - q) × 2n/2 + q;
}
10
C11 = M1 + M4 – M5 + M7;
C12 = M3 + M5;
C21 = M2 + M4;
C22 = M1 + M3 – M2 + M6;
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);
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
= P[n/2].x;
L = ClosestPair(PL, QL);
R = ClosestPair(PR, QR);
= min(L, R);
return min;
}
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;
}
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;
}