DAA Unit 3
DAA Unit 3
The cost of the algorithm is filing out the table. Addition is the basic operation. Because k ≤ n, the
sum needs to be split into two parts because only the half the table needs to be filled out
for i <k and remaining part of the table is filled out across the entire row.
A(n, k) = sum for upper triangle + sum for the lower rectangle
= ∑i=1k ∑j=1i-1 1 + ∑i=1n ∑j=1k 1
= ∑i=1k (i-1) + ∑i=1n k
= (k-1)k/2 + k(n-k) ϵ Θ(nk)
Time efficiency: Θ(nk)
Space efficiency: Θ(nk)
Using an identity called Pascal's Formula a recursive formulation for it looks like this:
This construction forms Each number in the triangle is the sum of the two numbers directly above
it.
FIGURE 3.1 (a) Digraph. (b) Its adjacency matrix. (c) Its transitive closure.
The transitive closure of a digraph can be generated with the help of depth-first search or
breadth-first search. Every vertex as a starting point yields the transitive closure for all.
Warshall’s algorithm constructs the transitive closure through a series of n × n boolean
matrices: R(0), . . . , R(k−1), R(k), . . . R(n).
The element rij(k) in the ith row and jth column of matrix R(k) (i, j = 1, 2, . . . , n, k = 0, 1, . . .
, n) is equal to 1 if and only if there exists a directed path of a positive length from the ith vertex to
the jth vertex with each intermediate vertex, if any, numbered not higher than k.
Steps to compute R(0), . . . , R(k−1), R(k), . . . R(n).
The series starts with R(0), which does not allow any intermediate vertices in its
paths; hence, R(0) is nothing other than the adjacency matrix of the digraph.
R(1) contains the information about paths that can use the first vertex as intermediate.
it may contain more 1’s than R(0).
The last matrix in the series, R(n), reflects paths that can use all n vertices of the
digraph as intermediate and hence is nothing other than the digraph’s transitive
closure.
In general, each subsequent matrix in series has one more vertex to use as
intermediate for its paths than its predecessor.
The last matrix in the series, R(n), reflects paths that can use all n vertices of the
digraph as intermediate and hence is nothing other than the digraph’s transitive
closure.
CS6402 Design and Analysis of Algorithms Unit III 3.4
All the elements of each matrix R(k) is computed from its immediate predecessor R(k−1). Let
rij , the element in the ith row and jth column of matrix R(k), be equal to 1. This means that there
(k)
exists a path from the ith vertex vi to the jth vertex vj with each intermediate vertex numbered not
higher than k.
The first part of this representation means that there exists a path from vi to vk with each
intermediate vertex numbered not higher than k − 1 (hence, rik(k−1) = 1), and the second part means
that there exists a path from vk to vj with each intermediate vertex numbered not higher than k − 1
(hence, rkj(k−1) = 1).
Thus the following formula generas the elements of matrix R(k) from the elements of matrix
(k−1)
R :
Warshall’s algorithm’s time efficiency is only Θ(n3). Space efficiency is Θ(n2). i.e matrix size.
CS6402 Design and Analysis of Algorithms Unit III 3.5
FIGURE 3.3 Application of Warshall’s algorithm to the digraph shown. New 1’s are in bold.
The series starts with D(0), which does not allow any intermediate vertices in its
paths; hence, D(0) is simply the weight matrix of the graph.
As in Warshall’s algorithm, we can compute all the elements of each matrix D(k)
from its immediate predecessor D(k−1).
The last matrix in the series, D(n), contains the lengths of the shortest paths among
all paths that can use all n vertices as intermediate and hence is nothing other than
the distance matrix.
Let dij(k) be the element in the ith row and the jth column of matrix D(k). This means that
k)
dij( is equal to the length of the shortest path among all paths from the ith vertex vi to the jth
vertex vj with their intermediate vertices numbered not higher than k.
The length of the shortest path can be computed by the following recurrence:
Floyd’s Algorithm’s time efficiency is only Θ(n3). Space efficiency is Θ(n2). i.e matrix size.
CS6402 Design and Analysis of Algorithms Unit III 3.7
FIGURE 3.5 Application of Floyd’s algorithm to the digraph shown. Updated elements are shown
in bold.
FIGURE 3.6 Two out of 14 possible binary search trees with keys A, B, C, and D.
Consider four keys A, B, C, and D to be searched for with probabilities 0.1, 0.2, 0.4, and
0.3, respectively. Figure 3.6 depicts two out of 14 possible binary search trees containing these
keys.
CS6402 Design and Analysis of Algorithms Unit III 3.8
The average number of comparisons in a successful search in the first of these trees is 0.1 .
1+ 0.2 . 2 + 0.4 . 3+ 0.3 . 4 = 2.9, and for the second one it is 0.1 . 2 + 0.2 . 1+ 0.4 . 2 + 0.3 . 3= 2.1.
Neither of these two trees is optimal.
The total number of binary search trees with n keys is equal to the nth Catalan number,
c(n)=(2n)!/(n+1)!n!
Let a1, . . . , an be distinct keys ordered from the smallest to the largest and let p1, . . . ,
pn be the probabilities of searching for them. Let C(i, j) be the smallest average number of
comparisons made in a successful search in a binary search tree Ti j made up of keys ai, . . . , aj,
where i, j are some integer indices, 1≤ i ≤ j ≤ n.
FIGURE 3.7 Binary search tree (BST) with root ak and two optimal binary search subtrees
Ti k−1 and T k+1 j.
Consider all possible ways to choose a root ak among the keys ai, . . . , aj . For such a binary
search tree (Figure 3.7), the root contains key ak, the left subtree Ti k−1 contains keys ai, . . . , ak−1
optimally arranged, and the right subtree T k+1 j contains keys ak+1, . . . , aj also optimally arranged.
If we count tree levels starting with 1 to make the comparison numbers equal the keys’
levels, the following recurrence relation is obtained:
FIGURE 3.8 Table of the dynamic programming algorithm for constructing an optimal binary
search tree.
The two-dimensional table in Figure 3.8 shows the values needed for computing C(i, j).
They are in row i and the columns to the left of column j and in column j and the rows below row i.
The arrows point to the pairs of entries whose sums are computed in order to find the smallest one
to be recorded as the value of C(i, j). This suggests filling the table along its diagonals, starting with
all zeros on the main diagonal and given probabilities pi, 1≤ i ≤ n, right above it and moving
toward the upper right corner.
EXAMPLE: Let us illustrate the algorithm by applying it to the four-key set we used at the
beginning of this section:
key A B C D
probability 0.1 0.2 0.4 0.3
The initial tables are:
Thus, out of two possible binary trees containing the first two keys, A and B, the root of the
optimal tree has index 2 (i.e., it contains B), and the average number of comparisons in a successful
search in this tree is 0.4.
We arrive at the following final tables:
Thus, the average number of key comparisons in the optimal tree is equal to 1.7. Since R(1,
4) = 3, the root of the optimal tree contains the third key, i.e., C. Its left subtree is made up of keys
A and B, and its right subtree contains just key D. To find the specific structure of these subtrees,
we find first their roots by consulting the root table again as follows. Since R(1, 2) = 2, the root of
the optimal tree containing A and B is B, with A being its left child (and the root of the one node
tree: R(1, 1) = 1). Since R(4, 4) = 4, the root of this one-node optimal tree is its only key D. Figure
3.10 presents the optimal tree in its entirety.
FIGURE 3.10 Optimal binary search tree for the above example.
CS6402 Design and Analysis of Algorithms Unit III 3.11
Thus, the value of an optimal solution among all feasible subsets of the first I items is the
maximum of these two values. Of course, if the ith item does not fit into the knapsack, the value of
an optimal subset selected from the first i items is the same as the value of an optimal subset
selected from the first i − 1 items. These observations lead to the following recurrence:
The maximal value is F(4, 5) = $37. We can find the composition of an optimal subset by
backtracing (Back tracing finds the actual optimal subset, i.e. solution), the computations of this
entry in the table. Since F(4, 5) > F(3, 5), item 4 has to be included in an optimal solution along
with an optimal subset for filling 5 − 2 = 3 remaining units of the knapsack capacity. The value of
the latter is F(3, 3). Since F(3, 3) = F(2, 3), item 3 need not be in an optimal subset. Since F(2, 3) >
F(1, 3), item 2 is a part of an optimal selection, which leaves element F(1, 3 − 1) to specify its
remaining composition. Similarly, since F(1, 2) > F(0, 2), item 1 is the final part of the optimal
solution {item 1, item 2, item 4}.
Table 3.3 Solving an instance of the knapsack problem by the dynamic programming algorithm.
Capacity j
i 0 1 2 3 4 5
0 0 0 0 0 0 0
w1 = 2, v1 = 12 1 0 0 12 12 12 12
w2 = 1, v2 = 10 2 0 10 12 22 22 22
w3 = 3, v3 = 20 3 0 10 12 22 30 32
w4 = 2, v4 = 15 4 0 10 15 25 30 37
CS6402 Design and Analysis of Algorithms Unit III 3.13
Memory Functions
The direct top-down approach to finding a solution to such a recurrence leads to an
algorithm that solves common subproblems more than once and hence is very inefficient.
The bottom up fills a table with solutions to all smaller subproblems, but each of them is
solved only once. An unsatisfying aspect of this approach is that solutions to some of these smaller
subproblems are often not necessary for getting a solution to the problem given.
Since this drawback is not present in the top-down approach, it is natural to try to combine
the strengths of the top-down and bottom-up approaches. The goal is to get a method that solves
only subproblems that are necessary and does so only once. Such a method exists; it is based on
using memory functions.
This method solves a given problem in the top-down manner but, in addition, maintains a
table of the kind that would have been used by a bottom-up dynamic programming algorithm.
Initially, all the table’s entries are initialized with a special “null” symbol to indicate that
they have not yet been calculated. Thereafter, whenever a new value needs to be calculated, the
method checks the corresponding entry in the table first: if this entry is not “null,” it is simply
retrieved from the table; otherwise, it is computed by the recursive call whose result is then
recorded in the table.
The following algorithm implements this idea for the knapsack problem. After initializing
the table, the recursive function needs to be called with i = n (the number of items) and j = W (the
knapsack capacity).
ALGORITHM MFKnapsack(i, j )
//Implements the memory function method for the knapsack problem
//Input: A nonnegative integer i indicating the number of the first items being considered
// and a nonnegative integer j indicating the knapsack capacity
//Output: The value of an optimal feasible subset of the first i items
//Note: Uses as global variables input arrays Weights [1..n], Values[1..n],
// and table F[0..n, 0..W ] whose entries are initialized with −1’s except for
// row 0 and column 0 initialized with 0’s
if F[i, j ]< 0
if j <Weights[i]
value←MFKnapsack(i − 1, j)
else
value←max(MFKnapsack(i − 1, j),
Values[i]+ MFKnapsack(i − 1, j −Weights[i]))
F[i, j ]←value
return F[i, j ]
EXAMPLE 2 Let us apply the memory function method to the instance considered in Example 1.
Capacity j
I 0 1 2 3 4 5
0 0 0 0 0 0 0
w1 = 2, v1 = 12 1 0 0 12 12 12 12
w2 = 1, v2 = 10 2 0 - 12 22 - 22
w3 = 3, v3 = 20 3 0 - - 22 - 32
w4 = 2, v4 = 15 4 0 - - - - 37
CS6402 Design and Analysis of Algorithms Unit III 3.14
Only 11 out of 20 nontrivial values (i.e., not those in row 0 or in column 0) have been
computed. Just one nontrivial entry, V (1, 2), is retrieved rather than being recomputed. For larger
instances, the proportion of such entries can be significantly larger.
Two classic algorithms for the minimum spanning tree problem: Prim’s algorithm and
Kruskal’s algorithm. They solve the same problem by applying the greedy approach in two
different ways, and both of them always yield an optimal solution.
Another classic algorithm named Dijkstra’s algorithm used to find the shortest-path in a
weighted graph problem solved by Greedy Technique . Huffman codes is an important data
compression method that can be interpreted as an application of the greedy technique.
The first way is one of the common ways to do the proof for Greedy Technique is by
mathematical induction.
The second way to prove optimality of a greedy algorithm is to show that on each step it does
at least as well as any other algorithm could in advancing toward the problem’s goal.
Example: find the minimum number of moves needed for a chess knight to go from one corner of a
100 × 100 board to the diagonally opposite corner. (The knight’s moves are L-shaped jumps: two
squares horizontally or vertically followed by one square in the perpendicular direction.)
A greedy solution is clear here: jump as close to the goal as possible on each move. Thus, if
its start and finish squares are (1,1) and (100, 100), respectively, a sequence of 66 moves such as
(1, 1) − (3, 2) − (4, 4) − . . . − (97, 97) − (99, 98) − (100, 100) solves the problem(The number k of
two-move advances can be obtained from the equation 1+ 3k = 100).
CS6402 Design and Analysis of Algorithms Unit III 3.15
Why is this a minimum-move solution? Because if we measure the distance to the goal by
the Manhattan distance, which is the sum of the difference between the row numbers and the
difference between the column numbers of two squares in question, the greedy algorithm decreases
it by 3 on each move.
The third way is simply to show that the final result obtained by a greedy algorithm is
optimal based on the algorithm’s output rather than the way it operates.
Example: Consider the problem of placing the maximum number of chips on an 8 × 8 board so that
no two chips are placed on the same or adjacent vertically, horizontally, or diagonally.
FIGURE 3.12 (a) Placement of 16 chips on non-adjacent squares. (b) Partition of the board
proving impossibility of placing more than 16 chips.
It is impossible to place more than one chip in each of these squares, which implies that the
total number of nonadjacent chips on the board cannot exceed 16.
A spanning tree of an undirected connected graph is its connected acyclic subgraph (i.e., a
tree) that contains all the vertices of the graph. If such a graph has weights assigned to its edges, a
minimum spanning tree is its spanning tree of the smallest weight, where the weight of a tree is
defined as the sum of the weights on all its edges. The minimum spanning tree problem is the
problem of finding a minimum spanning tree for a given weighted connected graph.
FIGURE 3.13 Graph and its spanning trees, with T1 being the minimum spanning tree.
ALGORITHM Prim(G)
//Prim’s algorithm for constructing a minimum spanning tree
//Input: A weighted connected graph G = {V, E}
//Output: ET, the set of edges composing a minimum spanning tree of G
VT←{v0} //the set of tree vertices can be initialized with any vertex
ET←Φ
for i ←1 to |V| − 1 do
find a minimum-weight edge e∗ = (v∗, u∗) among all the edges (v, u)
such that v is in VT and u is in V − VT
VT←VT∪ {u*}
ET←ET∪ {e*}
return ET
If a graph is represented by its adjacency lists and the priority queue is implemented as a
min-heap, the running time of the algorithm is O(|E| log |V |) in a connected graph, where |V| − 1≤
|E|.
CS6402 Design and Analysis of Algorithms Unit III 3.17
FIGURE 3.14 Application of Prim’s algorithm. The parenthesized labels of a vertex in the middle
column indicate the nearest tree vertex and edge weight; selected vertices and edges are in bold.
CS6402 Design and Analysis of Algorithms Unit III 3.18
The initial forest consists of |V | trivial trees, each comprising a single vertex of the graph.
The final forest consists of a single tree, which is a minimum spanning tree of the graph. On each
iteration, the algorithm takes the next edge (u, v) from the sorted list of the graph’s edges, finds the
trees containing the vertices u and v, and, if these trees are not the same, unites them in a larger tree
by adding the edge (u, v).
Fortunately, there are efficient algorithms for doing so, including the crucial check for
whether two vertices belong to the same tree. They are called union-find algorithms. With an
efficient union-find algorithm, the running time of Kruskal’s algorithm will be O(|E| log |E|).
CS6402 Design and Analysis of Algorithms Unit III 3.19
FIGURE 3.15 Application of Kruskal’s algorithm. Selected edges are shown in bold.
CS6402 Design and Analysis of Algorithms Unit III 3.20
ALGORITHM Dijkstra(G, s)
//Dijkstra’s algorithm for single-source shortest paths
//Input: A weighted connected graph G = (V, E) with nonnegative weights and its vertex s
//Output: The length dv of a shortest path from s to v and its penultimate vertex pv for every
// vertex v in V
Initialize(Q) //initialize priority queue to empty
for every vertex v in V
dv ← ∞; pv ← null
Insert (Q, v, dv) //initialize vertex priority in the priority queue
Ds ← 0; Decrease(Q, s, ds) //update priority of s with ds
VT ← Φ
for i ←0 to |V| − 1 do
u* ← DeleteMin(Q) //delete the minimum priority element
VT ←VT ∪ {u* }
for every vertex u in V − VT that is adjacent to u* do
if du* + w(u*, u) < du
du ← du* + w(u *, u); pu ← u*
Decrease(Q, u, du)
The time efficiency of Dijkstra’s algorithm depends on the data structures used for
implementing the priority queue and for representing an input graph itself. It is in Θ (|V |2) for
graphs represented by their weight matrix and the priority queue implemented as an unordered
array. For graphs represented by their adjacency lists and the priority queue implemented as a min-
heap, it is in O(|E| log |V |).
CS6402 Design and Analysis of Algorithms Unit III 3.21
FIGURE 3.16 Application of Dijkstra’s algorithm. The next closest vertex is shown in bold
The shortest paths (identified by following nonnumeric labels backward from a destination
vertex in the left column to the source) and their lengths (given by numeric labels of the tree
vertices) are as follows:
From a to b : a − b of length 3
From a to d : a − b − d of length 5
From a to c : a − b − c of length 7
From a to e : a − b − d − e of length 9
Huffman’s algorithm
Step 1 Initialize n one-node trees and label them with the symbols of the alphabet given.
Record the frequency of each symbol in its tree’s root to indicate the tree’s weight.
(More generally, the weight of a tree will be equal to the sum of the frequencies in
the tree’s leaves.)
Step 2 Repeat the following operation until a single tree is obtained. Find two trees with
the smallest weight (ties can be broken arbitrarily, but see Problem 2 in this
section’s exercises). Make them the left and right subtree of a new tree and record
the sum of their weights in the root of the new tree as its weight.
A tree constructed by the above algorithm is called a Huffman tree. It defines in the
manner described above is called a Huffman code.
EXAMPLE Consider the five-symbol alphabet {A, B, C, D, _} with the following occurrence
frequencies in a text made up of these symbols:
symbol A B C D _
frequency 0.35 0.1 0.2 0.2 0.15
The Huffman tree construction for this input is shown in Figure 3.18
symbol A B C D _
frequency 0.35 0.1 0.2 0.2 0.15
codeword 11 100 00 01 101
We used a fixed-length encoding for the same alphabet, we would have to use at least 3 bits
per each symbol. Thus, for this toy example, Huffman’s code achieves the compression ratio - a
standard measure of a compression algorithm’s effectiveness of (3− 2.25) / 3 ∙ 100% = 25%. In
other words, Huffman’s encoding of the text will use 25% less memory than its fixed-length
encoding.
Running time is O(n log n), as each priority queue operation takes time O( log n).