BCS401 Module 2
BCS401 Module 2
BRUTE FORCE APPROACHES (contd..): Exhaustive Search (Travelling Salesman problem and
Knapsack Problem).
DECREASE-AND-CONQUER: Insertion Sort, Topological Sorting.
DIVIDE AND CONQUER: Merge Sort, Quick Sort, Binary Tree Traversals, Multiplication of Large
Integers and Strassen’s Matrix Multiplication.
Chapter 3(Section 3.4), Chapter 4 (Sections 4.1,4.2), Chapter 5 (Section 5.1,5.2,5.3, 5.4)
LECTURE 9:
1. EXHAUSTIVE SEARCH .
For discrete problems in which no efficient solution method is known, it might be necessary to test
each possibility sequentially in order to determine if it is the solution. Such exhaustive examination
of all possibilities is known as exhaustive search, complete search or direct search. Exhaustive search
is simply a brute force approach to combinatorial problems (Minimization or maximization of
optimization problems and constraint satisfaction problems). Reason to choose brute-force /
exhaustive search approach as an important algorithm design strategy
1. First, unlike some of the other strategies, brute force is applicable to a very wide variety of
problems. In fact, it seems to be the only general approach for which it is more difficult to point out
problems it cannot tackle.
2. Second, for some important problems, e.g., sorting, searching, matrix multiplication, string
matching the brute-force approach yields reasonable algorithms of at least some practical value with
no limitation on instance size.
3. Third, the expense of designing a more efficient algorithm may be unjustifiable if only a few
instances of a problem need to be solved and a brute-force algorithm can solve those instances with
acceptable speed.
4. Fourth, even if too inefficient in general, a brute-force algorithm can still be useful for solving
small-size instances of a problem. Exhaustive Search is applied to the important problems like
Knapsack Problem
Assignment Problem.
The problem asks to find the shortest tour through a given set of n cities that visits each city exactly
once before returning to the city where it started.
The problem can be conveniently modelled by a weighted graph, with the graph’s vertices representing
the cities and the edge weights specifying the distances.
Then the problem can be stated as the problem of finding the shortest Hamiltonian circuit of the graph.
(A Hamiltonian circuit is defined as a cycle that passes through all the vertices of the graph exactly
once).
A Hamiltonian circuit can also be defined as a sequence of n + 1 adjacent vertices vi0, vi1, . . . , vin−1,
vi0, where the first vertex of the sequence is the same as the last one and all the other n − 1 vertices are
distinct. All circuits start and end at one particular vertex. Following Excercise presents a small instance
of the problem and its solution by this method.
We can get all the tours by generating all the permutations of n − 1 intermediate cities from a
particular city.. i.e. (n - 1)!
Consider two intermediate vertices, say, b and c, and then only permutations in which b precedes c.
(This trick implicitly defines a tour’s direction.)
An inspection of above exercises n reveals three pairs of tours that differ only by their direction.
Hence, we could cut the number of vertex permutations by half because cycle total lengths in both
directions are same.
1
The total number of permutations needed is still 2 (n − 1)!, which makes the exhaustive search
Review Questions:
Time efficiency: As given in the example, the solution to the instance of Figure 1.2.1 is given in
Figure 1.2.2. Since the number of subsets of an n-element set is 2n, the exhaustive search leads to a
Ω(2n) algorithm, no matter how efficiently individual subsets are generated.
Note: Exhaustive search of both the traveling salesman and knapsack problems leads to extremely
inefficient algorithms on every input. In fact, these two problems are the best-known examples of NP-
hard problems. No polynomial-time algorithm is known for any NP-hard problem. Moreover, most
computer scientists believe that such algorithms do not exist. some sophisticated approaches like
backtracking and branch-and-bound enable us to solve some instances but not all instances of these
in less than exponential time. Alternatively, we can use one of many approximation algorithms.
Review Questions:
In the decrease-by-a-constant variation, the size of an instance is reduced by the same constant on each
iteration of the algorithm. Typically, this constant is equal to one (Figure 4.1), although other constant
size reductions do happen occasionally.
Decrease by a constant factor
The decrease-by-a-constant-factor technique suggests reducing a problem instance by the same constant
factor on each iteration of the algorithm. In most applications, this constant factor is equal to two.
Example Problem:
The number of key comparisons in this algorithm obviously depends on the nature of the input. In the
worst case, A[j ]> v is executed the largest number of times, i.e., for every j = i − 1, . . . , 0. Since v =
A[i], it happens if and only if A[j ]>A[i] for j = i − 1, . . . , 0. Thus, for the worst-case input, we get
A[0]>A[1] (for i = 1), A[1]>A[2] (for i = 2), . . . , A[n − 2]>A[n − 1] (for i = n − 1). In other words, the
worst-case input is an array of strictly decreasing values. The number of key comparisons for such an
input is
It shows that on randomly ordered arrays, insertion sort makes on average half as many comparisons as
on decreasing arrays, i.e.,
Review Questions:
1. What is the fundamental idea behind the decrease-and-conquer technique?
2. Describe the three major variations of decrease-and-conquer with examples.
3. How insertion sort works and what technique it employs.
4. What is the key difference between decrease by a constant and decrease by a constant factor in
the decrease-and-conquer approach?
Note that a back edge in a DFS forest of a directed graph can connect a vertex to its parent. Whether or
not it is the case, the presence of a back edge indicates that the digraph has a directed cycle. A directed
cycle in a digraph is a sequence of three or more of its vertices that starts and ends with the same vertex
and in which every vertex is connected to its immediate predecessor by an edge directed from the
predecessor to the successor. For example, a, b, a is a directed cycle in the digraph in Figure. Conversely,
if a DFS forest of a digraph has no back edges, the digraph is a DAG, an acronym for directed acyclic
graph.
Topological sorting example:
Consider a set of five required courses {C1, C2, C3, C4, C5} a part-time student has to take in some
degree program. The courses can be taken in any order as long as the following course prerequisites are
met: C1 and C2 have no prerequisites, C3 requires C1 and C2, C4 requires C3, and C5 requires C3 and
C4. The student can take only one course per term. In which order should the student take the courses?
In terms of this digraph, the question is whether we can list its vertices in such an order that for every
edge in the graph, the vertex where the edge starts is listed before the vertex where the edge ends. (Can
you find such an ordering of this digraph’s vertices?) This problem is called topological sorting. It can
be posed for an arbitrary digraph, but it is easy to see that the problem cannot have a solution if a digraph
has a directed cycle. Thus, for topological sorting to be possible, a digraph in question must be a dag. It
turns out that being a dag is not only necessary but also sufficient for topological sorting to be possible;
i.e., if a digraph has no directed cycles, the topological sorting problem for it has a solution. Moreover,
there are two efficient algorithms that both verify whether a digraph is a dag and, if it is, produce an
ordering of vertices that solves the topological sorting problem.
The first algorithm is a simple application of depth-first search: perform a DFS traversal and note the
order in which vertices become dead-ends (i.e., popped off the traversal stack). Reversing this order
yields a solution to the topological sorting problem, provided, of course, no back edge has been
encountered during the traversal. If a back edge has been encountered, the digraph is not a dag, and
topological sorting of its vertices is impossible. Figure illustrates an application of this algorithm to the
digraph in above Figure. Note that in Figure-c, we have drawn the edges of the digraph, and they all
point from left to right as the problem’s statement requires. It is a convenient way to check visually the
correctness of a solution to an instance of the topological sorting problem.
Note that the solution obtained by the source-removal algorithm is different from the one obtained by
the DFS-based algorithm. Both of them are correct, of course; the topological sorting problem may have
several alternative solutions.
Review Questions:
1. What is a directed cycle in a digraph, and how does its presence relate to the concept of a DAG?
2. Using the example of course prerequisites, how topological sorting can be applied to solve a
scheduling problem.
3. Compare and contrast the DFS-based algorithm and the source-removal algorithm for
performing topological sorting. What are their main differences and similarities?
1. A problem is divided into several subproblems of the same type, ideally of about equal size.
2. The subproblems are solved (typically recursively, though sometimes a different algorithm is
employed, especially when subproblems become small enough).
3. If necessary, the solutions to the subproblems are combined to get a solution to the original
problem.
The divide-and-conquer technique as shown in Figure 2.9, which depicts the case of dividing a
problem into two smaller subproblems, then the subproblems solved separately. Finally solution to
the original problem is done by combining the solutions of subproblems.
In the most typical case of divide-and-conquer a problem’s instance of size n is divided into two
instances of size n/2. More generally, an instance of size n can be divided into b instances of size n/b,
with a of them needing to be solved. (Here, a and b are constants; a ≥ 1 and b > 1.) Assuming that size
n is a power of b to simplify our analysis, we get the following recurrence for the running time T (n):
T (n) = aT (n/b) + f (n),
Review Questions:
1. What is the basic principle of divide and conquer algorithms.
2. What are the three main steps involved in the divide-and-conquer technique?
3. What is the general form of a divide-and-conquer recurrence relation.
4. What are the key components of the recurrence relation T(n) = aT(n/b) + f(n)?
3.1MERGE SORT
It uses DIVIDE AND CONQUER method
Mergesort is a perfect example of a successful application of the divide-and conquer technique.
It sorts a given array A[0…n − 1] by dividing it into two halves A[0….(n/2)−1] and
A[(n/2)…..n−1], sorting each of them recursively, and then merging the two smaller sorted arrays
into a single sorted one.
For large n, the number of comparisons made by this algorithm in the average case turns out to be about
0.25n less and hence is also in Θ (n log n).
First, the algorithm can be implemented bottom up by merging pairs of the array’s elements, then
merging the sorted pairs, and so on. This avoids the time and space overhead of using a stack to handle
recursive calls. Second, we can divide a list to be sorted in more than two parts, sort each recursively,
and then merge them together. This scheme, which is particularly useful for sorting files residing on
secondary memory devices, is called multiway mergesort.
Review Questions:
1. How mergesort utilizes the divide-and-conquer method to sort an array.
2. What is the recurrence relation used to analyze the number of key comparisons in
mergesort?
3. According to the Master Theorem, what is the time complexity of mergesort in the worst
case scenario?
Obviously, after a partition is achieved, A[s] 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 A[s] independently.
difference with mergesort: there, the division of the problem into two subproblems is immediate and
the entire work happens in combining their solutions; here, the entire work happens in the division
stage, with no work required to combine the solutions to the subproblems.
In this type of sorting technique, we will be given n elements in the array A, and the first element is
made as “Pivot” element. The position next to pivot element as i and the last position as j. Then
following steps have to be done.
Steps:
1. we will now scan the subarray from both ends, comparing the subarray’s elements to the
pivot.
2. The left-to-right scan, denoted below by index pointer i, starts with the second element.
Since we want elements smaller than the pivot to be in the left part of the subarray, this scan
skips over elements that are smaller than the pivot and stops upon encountering the first
element greater than or equal to the pivot.
3. The right-to-left scan, denoted below by index pointer j, starts with the last element of the
subarray. Since we want elements larger than the pivot to be in the right part of the subarray,
this scan skips over elements that are larger than the pivot and stops on encountering the first
element smaller than or equal to the pivot.
4. After both scans stop, three situations may arise, depending on whether or not the scanning
indices have crossed. If scanning indices i and j have not crossed, i.e., i < j, we simply exchange
A[i] and A[j] and resume the scans by incrementing i and decrementing j, respectively:
ALGORITHM HoarePartition(A[l..r])
//Partitions a subarray by Hoare’s algorithm, using the first element as a
pivot
//Input: Subarray of array A[0..n − 1], defined by its left and right indices l
and r (l<r)
//Output: Partition of A[l..r], with the split position returned as this
function’s value
p←A[l]
i ←l; j ←r + 1
repeat
repeat i ←i + 1 until A[i]≥ p
repeat j ←j − 1 until A[j ]≤ p
swap(A[i], A[j ])
until i ≥ j
swap(A[i], A[j ]) //undo last swap when i ≥ j
swap(A[l], A[j ])
return j
Review Questions
1. What are the steps involved in the Quicksort algorithm using the divide-and-conquer
technique.
ALGORITHM Height(T )
//Computes recursively the height of a binary tree
//Input: A binary tree T
//Output: The height of T
if T = ∅
return −1
else
We measure the problem’s instance size by the number of nodes n(T ) in a given binary tree T .
Obviously, the number of comparisons made to compute the maximum of two numbers and the number
of additions A(n(T )) made by the algorithm are the same.
A(0) = 0.
The extra nodes (shown by little squares in Figure 5.5) are called external; the original nodes (shown
by little circles) are called internal.
The number of external nodes x is always 1 more than the number of internal nodes n:
To prove this equality, consider the total number of nodes, both internal and external. Since every node,
except the root, is one of the two children of an internal node, we have the equation
Note that equality (5.2) also applies to any nonempty full binary tree, in which, by definition, every
node has either zero or two children:
Returning to algorithm Height, the number of comparisons to check whether the tree is empty is
The most important divide-and-conquer algorithms for binary trees are the three classic traversals:
preorder, inorder, and postorder. All three traversals visit nodes of a binary tree recursively, i.e., by
visiting the tree’s root and its left and right subtrees. They differ only by the timing of the root’s visit:
As to their efficiency analysis, it is identical to the above analysis of the Height algorithm because a
recursive call is made for each node of an extended binary tree.
Finally,not all questions about binary trees require traversals of both left and right subtrees. we should
note that, For example, the search and insert operations for a binary search tree require processing only
one of the two subtrees. Accordingly, we considered them not as applications of divide-and conquer but
rather as examples of the variable-size-decrease technique.
Review Questions
1. Define a binary tree and What is the structure of a binary tree including its nodes, root, and
subtrees.
2. How does the divide-and-conquer technique apply to solving problems related to binary trees?
3. How is the height defined?
= 322
(2 ∗ 1) and (3 ∗ 4) are already computed used. So only one multiplication only we have to do.
For any pair of two-digit numbers a = a1a0 and b = b1b0, their product c can be computed by the formula
c = a ∗ b = c2102 + c1101 + c0, where
c1 = (a1 + a0) ∗ (b1 + b0) − (c2 + c0) is the product of the sum of the a’s digits and the sum of
the b’s digits minus the sum of c2 and c0.
Now we apply this trick to multiplying two n-digit integers a and b where n is a positive even number.
Let us divide both numbers in the middle to take advantage of the divide-and conquer technique.
We denote the first half of the a’s digits by a1 and the second half by a0; for b, the notations are b1 and
b0, respectively. In these notations, a = a1a0 implies that a = a110n/2 + a0 and b = b1b0 implies that b =
b110n/2 + b0. Therefore, taking advantage of the same trick we used for two-digit numbers, we get
C=a∗b
If n/2 is even, we can apply the same method for computing the products c2, c0, and c1.
Thus, if n is a power of 2, we have a recursive algorithm for computing the product of two n-digit
integers. In its pure form, the recursion is stopped when n becomes 1. It can also be stopped when
we deem n small enough to multiply the numbers of that size directly.
The multiplication of n-digit numbers requires three multiplications of n/2-digit numbers, the recurrence
for the number of multiplications M(n) is
M(1) = 1.
(On the last step, we took advantage of the following property of logarithms: a log b c = c log b a.) Let A(n)
be the number of digit additions and subtractions executed by the above algorithm in multiplying two
n-digit decimal integers. Besides 3A(n/2) of these operations needed to compute the three products of
n/2-digit numbers, the above formulas require five additions and one subtraction. Hence, we have the
recurrence
A(1) = 1
just seven multiplications as opposed to the eight required by the brute-force algorithm.
The value C00 can be computed either as A00 * B00 + A01 * B10 or as M1 + M4 − M5 + M7 where
M1, M4, M5, and M7 are found by Strassen’s formulas, with the numbers replaced by the corresponding
The asymptotic efficiency of Strassen’s matrix multiplication algorithm If M(n) is the number of
multiplications made by Strassen’s algorithm in multiplying two n×n matrices, where n is a power of 2,
The recurrence relation is M(n) = 7M(n/2) for n > 1, M(1)=1.
which is smaller than n 3 required by the brute-force algorithm. Since this savings in the number of
multiplications was achieved at the expense of making extra additions, we must check the number of
additions A(n) made by Strassen’s algorithm. To multiply two matrices of order n>1, the algorithm
needs to multiply seven matrices of order n/2 and make 18 additions/subtractions of matrices of size
n/2; when n = 1, no additions are made since two numbers are simply multiplied. These observations
yield the following recurrence relation:
A(1) = 0.
By closed-form solution to this recurrence and the Master Theorem, A(n) ∈ Θ(nlog 2 7). which is a
better efficiency class than Θ(n3 )of the brute-force method.
Example: Multiply the following two matrices by Strassen’s matrix multiplication algorithm.
15. Sort the list E,X,A,M,P,L,E in alphabetical order using MergeSort. Also discuss the efficiency.
16. Apply Quick sort to sort the list Q,U,E,S,T,I,O,N.
17. Consider the numbers given below.Show how the partition algorithm of quicksort will place
106 in its correct place. Show all the steps.
106,117,129,114,141,91,84,63,42