Daa Ktu Notes
Daa Ktu Notes
Daa Ktu Notes
3+1+0
Module 2 Masters Theorm (Proof not required) – examples, Asymptotic Notations and
their properties- Application of Asymptotic Notations in Algorithm Analysis-
Common Complexity Functions.
AVL Trees – rotations, Red-Black Trees insertion and deletion (Techniques only;
algorithm not expected). B- Trees – insertion and deletion operations. Sets – Union
and find operations on disjoint sets.
dot co ICET, Mulavoor
Module 3 Graphs
DFS and BFS traversals, complexity, Spanning trees- Minimum Cost Spanning Trees,
single source shortest path algorithms, Topological sorting, strongly connected
components.
The control Abstraction, 2 way Merge sort, Strassen‘s Matrix Multiplication, Analysis
Module 5
Module 6
Back Tracking:- The control Abstraction – The N Queen‘s Problem, 0/1 Knapsack
Problem.
Text Book
References
1. Computer Algorithms – Introduction to Design and Analysis - Sara Baase & Allen Van
Gelder, Pearson Education
MODULE 1
ALGORITHM
Informal Definition:
Formal Definition:
Properties of an Algorithm
Development of an Algorithm
When this way is selected, we should ensure that each and every statement is definite.
This method will work well when the algorithm is small& simple.
3. Pseudo-code Method:
PSEUDO-CODE CONVENTIONS:
3. An identifier begins with a letter. The data types of variables are not explicitly
declared.
Node. Record
node * link;
Here link is a pointer to the record type node. Individual data items of a record
can be accessed with and period.
While Loop:
<statement-1>
<statement-n>
For Loop:
<statement-1>
<statement-n>
repeat-until:
repeat
<statement-1>
dot co ICET, Mulavoor
<statement-n>
until<condition>
Else <statement-1>
Case statement:
Case
: <condition-1> : <statement-1>
: <condition-n> : <statement-n>
: else : <statement-n+1>
9. Input and output are done using the instructions read & write.
Example
1. algorithm Max(A,n)
2. // A is an array of size n
3. {
4. Result := A[1];
5. for I:= 2 to n do
6. if A[I] > Result then
7. Result :=A[I];
8. return Result;
9. }
PERFORMANCE ANALYSIS:
1. Space Complexity:
The space complexity of an algorithm is the amount of money it needs to
run to compilation.
2. Time Complexity:
The time complexity of an algorithm is the amount of computer time it
needs to run to compilation.
Space Complexity:
Space Complexity
dot co ICET, Mulavoor
Example 1:
Algorithm abc(a,b,c)
The Space needed by each of these algorithms is seen to be the sum of the following
component.
1. A fixed part that is independent of the characteristics (eg: number, size) of the inputs and
outputs.
The part typically includes the instruction space (ie. Space for the code), space for simple
variable and fixed-size component variables (also called aggregate) space for constants, and
so on.
1. A variable part that consists of the space needed by component variables whose size
is dependent on the particular problem instance being solved, the space needed by referenced
variables (to the extent that is depends on instance characteristics), and the recursion stack
space.
The space requirement s(p) of any algorithm p may therefore be written as,
Example 2:
Algorithm sum(a,n)
dot co ICET, Mulavoor
s=0.0;
for I=1 to n do
s= s+a[I];
return s;
Time Complexity:
The time T(p) taken by a program P is the sum of the compile time and the run
time(execution time)
The compile time does not depend on the instance characteristics. Also we may assume
that a compiled program will be run several times without recompilation .This rum time is
denoted by tp(instance characteristics).
Interactive statement such as for, while & repeat-until Control part of the
statement.
1. We introduce a variable, count into the program statement to increment count with
initial value 0.Statement to increment count by the appropriate amount are introduced into the
program.
This is done so that each time a statement in the original program is
executes count is incremented by the step count of that statement.
Eg:-
Algorithm sum(a,n)
s= 0.0;
count = count+1;
for I=1 to n do
count =count+1;
s=s+a[I];
count=count+1;
dot co ICET, Mulavoor
count=count+1;
count=count+1;
return s;
If the count is zero to start with, then it will be 2n+3 on termination. So each invocation
of sum execute a total of 2n+3 steps.
table in which we list the total number of steps contributes by each statement.
First determine the number of steps per execution (s/e) of the statement and the
By combining these two quantities, the total contribution of all statements, the
2.{ 0 - 0
3. S=0.0; 1 1 1
5. s=s+a[I]; 1 n n
6. return s; 1 1 1
7. } 0 - 0
dot co ICET, Mulavoor
2n+3
Total
ANALYSIS
The worst-case complexity of the algorithm is the function defined by the maximum
number of steps taken on any instance of size n. It represents the curve passing through the
highest point of each column.
The best-case complexity of the algorithm is the function defined by the minimum number
of steps taken on any instance of size n. It represents the curve passing through the lowest
point of each column. For example, the best case for a simple linear search on a list occurs
when the desired element is the first element of the list.
dot co ICET, Mulavoor
Finally, the average-case complexity of the algorithm is the function defined by the average
number of steps taken on any instance of size n.
Complexity:
Complexity refers to the rate at which the storage time grows as a function of the
problem size.
RECURRENCE RELATIONS
The recurrence relation is an equation or inequality that describes a function in terms of its
values of smaller inputs. The main tool for analyzing the time efficiency of a recurrence
algorithm is to setup a sum expressing the number executions of its basic operation and
ascertain the solution‗s order of growth.
To solve a recurrence relation means, to obtain a function defined on natural numbers that
satisfies the recurrence.
dot co ICET, Mulavoor
– Substitution method
– Master method
– Iteration method
=...
A different way to look at the iteration method: is the recursion-tree .we draw out the
recursion tree with cost of single call in each node—running time is sum of costs in all nodes
if you are careful drawing the recursion tree and summing up the costs, the recursion tree is a
direct proof for the solution of the recurrence, just like iteration and substitution
(1) Total =
= (n2)
L2.18
Amortized analysis
Amortized analysis is a method of analyzing algorithms that considers the entire sequence of
operations of the program to show that the average cost per operation is small, even though a
single operation within the sequence might be expensive.
An amortized analysis guarantees the average performance of each operation in the worst
case.
Amortized analysis is not just an analysis tool, it is also a way of thinking about designing
algorithms.
Note
dot co ICET, Mulavoor
• Show that for all n, a sequence of n operations take worst-case time T(n) in total
• In the worst case, the average cost, or amortized cost , per operation is T(n)/n.
• The amortized cost applies to each operation, even when there are several types of
operations in the sequence.
Example
• Stack operations:
– PUSH(S,x), O(1)
– POP(S), O(1)
• do POP(S)
• k=k-1
• Let us consider a sequence of n PUSH, POP, MULTIPOP.
– The worst case cost for MULTIPOP in the sequence is O(n), since the stack
size is at most n.
– Thus the cost of the sequence is O(n2). Correct, but not tight.
If any sequence of n operations on a data structure takes _ T(n) time, the amortized time per
operation is T(n)/n, which is O(1).
In aggregate analysis, all operations have the same amortized cost (total cost divided by n)
dot co ICET, Mulavoor
MODULE II
MASTERS THEOREM
The master method is used for solving the following type of recurrence
In above recurrence the problem is divided in to ‗a‘ subproblems each of size atmost
‗n/b‘.The subproblems are solved recursively each in T(n/b) time.The cost of split the
problem or combine the solutions of subproblems is given by function f(n).It should be note
THEOREM
asymtotically as,
ASYMPTOTIC NOTATIONS
Step count is to compare time complexity of two programs that compute same function and
also to predict the growth in run time as instance characteristics changes.Determining exact
step count is difficult and not necessary also. Since the values are not exact quantities we
need only comparative statements like c1n2 = tp(n) = c2n2.For example, consider two
programs with complexities c1n2 + c2n and c3n respectively.For small values of n,
complexity depend upon values of c1, c2 and c3. But there will also be an n beyond which
complexity of c3n is better than that of c1n2 + c2n.This value of n is called break-even point.
If this point is zero, c3n is always faster (or at least as fast).
This notation gives an upper bound for a function to within a constant factor. We
write f(n) = O(g(n)) if there are positive constants n0 and c such that to the right of n0,
the value of f(n) always lies on or below cg(n).
Big-O, commonly written as O, is an Asymptotic Notation for the worst case, or ceiling of
growth for a given function. It provides us with an asymptotic upper bound for the growth
rate of runtime of an algorithm. Say f(n) is your algorithm runtime, and g(n) is an
arbitrary time complexity you are trying to relate to your algorithm. f(n) is O(g(n)), if for
some real constants c (c > 0) and n0, f(n) <= c g(n) for every input size n (n > n0).
dot co ICET, Mulavoor
Linear Functions
Example 1
f(n) = 3n + 2
When n = 2, 3n + 2 = 3n + n = 4n
When n = 1, 3n + 2 = 3n + 2n = 5n
Hence we can have different c,n0 pairs satisfying for a given function.
Example 2
f(n) = 10n2 + 4n + 2
Example 3
f(n) = 6*2n + n2
dot co ICET, Mulavoor
When n = 4, n2 = 2n
Constant Functions
2. Big-Omega Notation
For non-negative functions, f(n) and g(n), if there exists an integer n0 and a constant c > 0
such that for all integers n > n0, f(n) ≥ cg(n), then f(n) is omega of g(n). This is denoted as
"f(n) = Ω(g(n))".
f(n) is Ω(g(n)), if for some real constants c (c > 0) and n0 (n0 > 0), f(n) is >= c g(n) for
every input size n (n > n0).
This notation gives a lower bound for a function to within a constant factor. We write f(n)
= Ω(g(n)) if there are positive constants n0 and c such that to the right of n0, the value of
f(n) always lies on or above cg(n).
dot co ICET, Mulavoor
If f(n) = T(g(n)), all values of n right to n0 f(n) lies on or above c1g(n) and on or below
The theta notation is more precise than both the big oh and big omega notations. The
function f(n)=theta(g(n)) iff g(n) is both lower and upper bound of f(n).
Example 1
f(n) = 3n + 2
dot co ICET, Mulavoor
It defines the asymptotic tight upper bound. Main difference with Big Oh is that Big Oh
.(g(n)) = { f(n) : for any positive constants c>0 and n0>0 such that 0 = cg(n) < f(n) for all
n = n0 }
It defines the asymptotic tight lower bound. Main difference with O is that, . defines for
Temporal comparison is not the only issue in algorithms. There are space issues as well.
Generally, a tradeoff between time and space is noticed in algorithms. Asymptotic notation
empowers you to make that trade off. If you think of the amount of time and space your
algorithm uses as a function of your data over time or space (time and space are usually
analyzed separately), you can analyze how the time and space is handled when you introduce
more data to your program.
This is important in data structures because you want a structure that behaves efficiently as
you increase the amount of data it handles. Keep in mind though that algorithm that are
efficient with large amounts of data are not always simple and efficient for small amounts of
data. So if you know you are working with only a small amount of data and you have
concerns for speed and code space, a trade off can be made for a function that does not
dot co ICET, Mulavoor
Generally, we use asymptotic notation as a convenient way to examine what can happen in a
function in the worst case or in the best case. For example, if you want to write a function
that searches through an array of numbers and returns the smallest one:
let j :=
for i := 1 to n:
j := min(j, a[i])
repeat
return j
end
Regardless of how big or small the array is, every time we run find-min, we have to initialize
the i and j integer variables and return j at the end. Therefore, we can just think of those parts
of the function as constant and ignore them.
So, how can we use asymptotic notation to discuss the find-min function? If we search
through an array with 87 elements, then the for loop iterates 87 times, even if the very first
element we hit turns out to be the minimum. Likewise, for n elements, the for loop iterates n
times. Therefore we say the function runs in time O(n).
let j := ;
dot co ICET, Mulavoor
for i := 1 to n:
j := min(j, a[i])
repeat
let minim := j
j := ;
for i := 1 to n:
j := max(j, a[i])
repeat
let maxim := j
end
What's the running time for find-min-plus-max? There are two for loops, that each iterate n
times, so the running time is clearly O(2n). Because 2 is a constant, we throw it away and
write the running time as O(n). Why can you do this? If you recall the definition of Big-O
notation, the function whose bound you're testing can be multiplied by some constant. If
f(x) = 2x, we can see that if g(x) = x, then the Big-O condition holds. Thus O(2n) = O(n). This
rule is general for the various asymptotic notations.
Comparison of functions
Many of the relational properties of real numbers apply to asymptotic comparisons as well.
For the following, assume that f(n) and g(n) are asymptotically positive.
Transitivity:
Reflexivity:
f(n)=(f(n)),
f(n)=O(f(n)),
f(n)=Ω(f(n)).
Symmetry:
AVL TREES
AVL tree is a self-balancing Binary Search Tree (BST) where the difference between heights
of left and right subtrees cannot be more than one for all nodes.
The above tree is AVL because differences between heights of left and right subtrees for
dot co ICET, Mulavoor
The above tree is not AVL because differences between heights of left and right subtrees for
8 and 18 are greater than 1.
height of an AVL tree is always O(Logn) where n is the number of nodes in the tree (See this
video lecture for proof).
Insertion
To make sure that the given tree remains AVL after every insertion, we must augment the
standard BST insert operation to perform some re-balancing. Following are two basic
operations that can be performed to re-balance a BST without violating the BST property
(keys(left) < key(root) < keys(right)). 1) Left Rotation 2) Right Rotation
dot co ICET, Mulavoor
Following are the operations to be performed in above mentioned 4 cases. In all of the cases,
we only need to re-balance the subtree rooted with z and the complete tree becomes balanced
as the height of subtree (After appropriate rotations) rooted with z becomes same as it was
before insertion.
Red-Black Tree is a self-balancing Binary Search Tree (BST) where every node follows following
rules.
WhyRed-BlackTrees?
Most of the BST operations (e.g., search, max, min, insert, delete.. etc) take O(h) time where
h is the height of the BST. The cost of these operations may become O(n) for a skewed
Binary tree. If we make sure that height of the tree remains O(Logn) after every insertion and
deletion, then we can guarantee an upper bound of O(Logn) for all these operations. The
height of a Red Black tree is always O(Logn) where n is the number of nodes in the tree.
From the above examples, we get some idea how Red-Black trees ensure balance. Following
is an important fact about balancing in Red-Black Trees.
Every Red Black Tree with n nodes has height <= 2Log2(n+1)
1) For a general Binary Tree, let k be the minimum number of nodes on all root to NULL
paths, then n >= 2k – 1 (Ex. If k is 3, then n is atleast 7). This expression can also be written
as k <= 2Log2(n+1)
2) From property 4 of Red-Black trees and above claim, we can say in a Red-Black Tree with
n nodes, there is a root to leaf path with at-most Log2(n+1) black nodes.
3) From property 3 of Red-Black trees, we can claim that the number black nodes in a Red-
Black tree is at least ⌊ n/2 ⌋ where n is total number of nodes.
From above 2 points, we can conclude the fact that Red Black Tree with n nodes has height
<= 2Log2(n+1)
In this post, we introduced Red-Black trees and discussed how balance is ensured. The hard
part is to maintain balance when keys are added and removed. We will soon be discussing
insertion and deletion operations in coming posts on Red-Black tree.
In AVL tree insertion, we used rotation as a tool to do balancing after insertion caused
imbalance. In Red-Black tree, we use two tools to do balancing.
1) Recoloring
2) Rotation
We try recoloring first, if recoloring doesn‘t work, then we go for rotation. Following is
detailed algorithm. The algorithms has mainly two cases depending upon the color of uncle.
If uncle is red, we do recoloring. If uncle is black, we do rotations and/or recoloring.
1) Perform standard BST insertion and make the color of newly inserted nodes as RED.
2) If x is root, change color of x as BLACK (Black height of complete tree increases by 1).
3) Do following if color of x‘s parent is not BLACK or x is not root.
….a) If x’s uncle is RED (Grand parent must have been black from property 4)
……..(i) Change color of parent and uncle as BLACK.
……..(ii) color of grand parent as RED.
……..(iii) Change x = x‘s grandparent, repeat steps 2 and 3 for new x.
….b) If x’s uncle is BLACK, then there can be four configurations for x, x‘s parent (p) and
x‘s grandparent (g) (This is similar to AVL Tree)
Insertion Vs Deletion:
Like Insertion, recoloring and rotations are used to maintain the Red-Black properties.
In insert operation, we check color of uncle to decide the appropriate case. In delete
operation, we check color of sibling to decide the appropriate case.
The main property that violates after insertion is two consecutive reds. In delete, the main
violated property is, change of black height in subtrees as deletion of a black node may cause
reduced black height in one root to leaf path.
Deletion is fairly complex process. To understand deletion, notion of double black is used.
When a black node is deleted and replaced by a black child, the child is marked as double
black. The main task now becomes to convert this double black to single black.
Deletion Steps
1) Perform standard BST delete. When we perform standard delete operation in BST, we
always end up deleting a node which is either leaf or has only one child (For an internal node,
we copy the successor and then recursively call delete for successor, successor is always a
dot co ICET, Mulavoor
leaf node or a node with one child). So we only need to handle cases where a node is leaf or
has one child. Let v be the node to be deleted and u be the child that replaces v (Note that u is
NULL when v is a leaf and color of NULL is considered as Black).
2) Simple Case: If either u or v is red, we mark the replaced child as black (No change in
black height). Note that both u and v cannot be red as v is parent of u and two consecutive
reds are not allowed in red-black tree.
3.1) Color u as double black. Now our task reduces to convert this double black to single
black. Note that If v is leaf, then u is NULL and color of NULL is considered as black. So the
deletion of a black leaf also causes a double black.
3.2) Do following while the current node u is double black and it is not root. Let sibling of
node be s.
dot co ICET, Mulavoor
….(a): If sibling s is black and at least one of sibling’s children is red, perform rotation(s).
Let the red child of s be r. This case can be divided in four subcases depending upon
positions of s and r.
…………..(i) Left Left Case (s is left child of its parent and r is left child of s or both children
of s are red). This is mirror of right right case shown in below diagram.
…………..(ii) Left Right Case (s is left child of its parent and r is right child). This is mirror
of right left case shown in below diagram.
…………..(iii) Right Right Case (s is right child of its parent and r is right child of s or both
children of s are red)
…………..(iv) Right Left Case (s is right child of its parent and r is left child of s)
…..(b): If sibling is black and its both children are black, perform recoloring, and
recur for the parent if parent is black.
In this case, if parent was red, then we didn‘t need to recur for prent, we can simply make it
black (red + double black = single black)
…..(c): If sibling is red, perform a rotation to move old sibling up, recolor the old sibling
and parent. The new sibling is always black (See the below diagram). This mainly converts
the tree to black sibling case (by rotation) and leads to case (a) or (b). This case can be
divided in two subcases.
…………..(i) Left Case (s is left child of its parent). This is mirror of right right case shown
in below diagram. We right rotate the parent p.
…………..(iii) Right Case (s is right child of its parent). We left rotate the parent p.
3.3) If u is root, make it single black and return (Black height of complete tree reduces by 1).
B- Trees
B-Tree is a self-balancing search tree. In most of the other self-balancing search trees
(like AVL and Red Black Trees), it is assumed that everything is in main memory. To
understand use of B-Trees, we must think of huge amount of data that cannot fit in main
memory.When the number of keys is high, the data is read from disk in the form of blocks.
Disk access time is very high compared to main memory access time. The main idea of using
B-Trees is to reduce the number of disk accesses. Most of the tree operations (search, insert,
delete, max, min, ..etc ) require O(h) disk accesses where h is height of the tree. B-tree is a fat
tree. Height of B-Trees is kept low by putting maximum possible keys in a B-Tree node.
Generally, a B-Tree node size is kept equal to the disk block size. Since h is low for B-Tree,
total disk accesses for most of the operations are reduced significantly compared to balanced
Binary Search Trees like AVL Tree, Red Black Tree, ..etc.
Properties of B-Tree
Search
Search is similar to search in Binary Search Tree. Let the key to be searched be k. We start
from root and recursively traverse down. For every visited non-leaf node, if the node has key,
we simply return the node. Otherwise we recur down to the appropriate child (The child
which is just before the first greater key) of the node. If we reach a leaf node and don‘t find k
in the leaf node, we return NULL.
dot co ICET, Mulavoor
Traverse
Traversal is also similar to Inorder traversal of Binary Tree. We start from the leftmost child,
recursively print the leftmost child, then repeat the same process for remaining children and
keys. In the end, recursively print the rightmost child.
Insert
In this post, insert() operation is discussed. A new key is always inserted at leaf node. Let the
key to be inserted be k. Like BST, we start from root and traverse down till we reach a leaf
node. Once we reach a leaf node, we insert the key in that leaf node. Unlike BSTs, we have a
predefined range on number of keys that a node can contain. So before inserting a key to
node, we make sure that the node has extra space.
How to make sure that a node has space available for key before the key is inserted? We use
an operation called splitChild() that is used to split a child of a node. See the following
diagram to understand split. In the following diagram, child y of x is being split into two
nodes y and z. Note that the splitChild operation moves a key up and this is the reason B-
Trees grow up unlike BSTs which grow down.
As discussed above, to insert a new key, we go down from root to leaf. Before traversing
down to a node, we first check if the node is full. If the node is full, we split it to create space.
Following is complete algorithm.
Insertion
1) Initialize x as root.
2) While x is not leaf, do following
..a) Find the child of x that is going to to be traversed next. Let the child be y.
..b) If y is not full, change x to point to y.
..c) If y is full, split it and change x to point to one of the two parts of y. If k is smaller than
mid key in y, then set x as first part of y. Else second part of y. When we split y, we move a
key from y to its parent x.
3) The loop in step 2 stops when x is leaf. x must have space for 1 extra key as we have been
splitting all nodes in advance. So simply insert k to x.
Note that the algorithm follows the Cormen book. It is actually a proactive insertion
algorithm where before going down to a node, we split it if it is full. The advantage of
splitting before is, we never traverse a node twice. If we don‘t split a node before going down
to it and split it only if new key is inserted (reactive), we may end up traversing all nodes
dot co ICET, Mulavoor
again from leaf to root. This happens in cases when all nodes on the path from root to leaf are
full. So when we come to the leaf node, we split it and move a key up. Moving a key up will
cause a split in parent node (because parent was already full). This cascading effect never
happens in this proactive insertion algorithm. There is a disadvantage of this proactive
insertion though, we may do unnecessary splits.
Let us understand the algorithm with an example tree of minimum degree ‗t‘ as 3 and a
sequence of integers 10, 20, 30, 40, 50, 60, 70, 80 and 90 in an initially empty B-Tree.
Initially root is NULL. Let us first insert 10.
Let us now insert 20, 30, 40 and 50. They all will be inserted in root because maximum
number of keys a node can accommodate is 2*t – 1 which is 5.
Let us now insert 60. Since root node is full, it will first split into two, then 60 will be
inserted into the appropriate child.
Let us now insert 70 and 80. These new keys will be inserted into the appropriate leaf
without any split.
dot co ICET, Mulavoor
Let us now insert 90. This insertion will cause a split. The middle key will go up to the
parent.
Deletion Process
Deletion from a B-tree is more complicated than insertion, because we can delete a key from
any node-not just a leaf—and when we delete a key from an internal node, we will have to
rearrange the node‘s children.
As in insertion, we must make sure the deletion doesn‘t violate the B-tree properties. Just
as we had to ensure that a node didn‘t get too big due to insertion, we must ensure that a node
doesn‘t get too small during deletion (except that the root is allowed to have fewer than the
minimum number t-1 of keys). Just as a simple insertion algorithm might have to back up if a
node on the path to where the key was to be inserted was full, a simple approach to deletion
might have to back up if a node (other than the root) along the path to where the key is to be
deleted has the minimum number of keys.
The deletion procedure deletes the key k from the subtree rooted at x. This procedure
guarantees that whenever it calls itself recursively on a node x, the number of keys in x is at
least the minimum degree t . Note that this condition requires one more key than the
minimum required by the usual B-tree conditions, so that sometimes a key may have to be
moved into a child node before recursion descends to that child. This strengthened condition
allows us to delete a key from the tree in one downward pass without having to ―back up‖
(with one exception, which we‘ll explain). You should interpret the following specification
for deletion from a B-tree with the understanding that if the root node x ever becomes an
internal node having no keys (this situation can occur in cases 2c and 3b then we delete x,
and x‘s only child x.c1 becomes the new root of the tree, decreasing the height of the tree by
one and preserving the property that the root of the tree contains at least one key (unless the
tree is empty).
We sketch how deletion works with various cases of deleting keys from a B-tree.
1. If the key k is in node x and x is a leaf, delete the key k from x.
2. If the key k is in node x and x is an internal node, do the following.
a) If the child y that precedes k in node x has at least t keys, then find the predecessor k0 of
k in the sub-tree rooted at y. Recursively delete k0, and replace k by k0 in x. (We can find k0
and delete it in a single downward pass.)
b) If y has fewer than t keys, then, symmetrically, examine the child z that follows k in
node x. If z has at least t keys, then find the successor k0 of k in the subtree rooted at z.
Recursively delete k0, and replace k by k0 in x. (We can find k0 and delete it in a single
downward pass.)
c) Otherwise, if both y and z have only t-1 keys, merge k and all of z into y, so that x loses
both k and the pointer to z, and y now contains 2t-1 keys. Then free z and recursively delete k
from y.
dot co ICET, Mulavoor
3. If the key k is not present in internal node x, determine the root x.c(i) of the appropriate
subtree that must contain k, if k is in the tree at all. If x.c(i) has only t-1 keys, execute step 3a
or 3b as necessary to guarantee that we descend to a node containing at least t keys. Then
finish by recursing on the appropriate child of x.
a) If x.c(i) has only t-1 keys but has an immediate sibling with at least t keys, give x.c(i) an
extra key by moving a key from x down into x.c(i), moving a key from x.c(i) ‘s immediate
left or right sibling up into x, and moving the appropriate child pointer from the sibling into
x.c(i).
b) If x.c(i) and both of x.c(i)‘s immediate siblings have t-1 keys, merge x.c(i) with one
sibling, which involves moving a key from x down into the new merged node to become the
median key for that node.
Since most of the keys in a B-tree are in the leaves, deletion operations are most often used to
delete keys from leaves. The recursive delete procedure then acts in one downward pass
through the tree, without having to back up. When deleting a key in an internal node,
however, the procedure makes a downward pass through the tree but may have to return to
the node from which the key was deleted to replace the key with its predecessor or successor
(cases 2a and 2b).
The following figures from CLRS book explain the deletion porcess.
Sets
Refer text.
MODULE III
Graphs
Graph is a data structure that consists of following two components:
1. A finite set of vertices also called as nodes.
The pair is ordered because (u, v) is not same as (v, u) in case of directed graph(di-graph).
The pair of form (u, v) indicates that there is an edge from vertex u to vertex v. The edges
may contain weight/value/cost.
Graphs are used to represent many real life applications: Graphs are used to represent
networks. The networks may include paths in a city or telephone network or circuit network.
Graphs are also used in social networks like linkedIn, facebook. For example, in facebook,
each person is represented with a vertex(or node). Each node is a structure and contains
information like person id, name, gender and locale. See this for more applications of graph.
Following is an example undirected graph with 5 vertices.
dot co ICET, Mulavoor
Adjacency Matrix:
Pros: Representation is easier to implement and follow. Removing an edge takes O(1) time.
Queries like whether there is an edge from vertex ‗u‘ to vertex ‗v‘ are efficient and can be
done O(1).
Cons: Consumes more space O(V^2). Even if the graph is sparse(contains less number of
edges), it consumes the same space. Adding a vertex is O(V^2) time.
Adjacency List:
An array of linked lists is used. Size of the array is equal to number of vertices. Let the array
dot co ICET, Mulavoor
be array[]. An entry array[i] represents the linked list of vertices adjacent to the ith vertex.
This representation can also be used to represent a weighted graph. The weights of edges can
be stored in nodes of linked lists. Following is adjacency list representation of the above
graph.
For example, in the following graph, we start traversal from vertex 2. When we come to
vertex 0, we look for all adjacent vertices of it. 2 is also an adjacent vertex of 0. If we don‘t
mark visited vertices, then 2 will be processed again and it will become a non-terminating
process. A Breadth First Traversal of the following graph is 2, 0, 3, 1.
Depth First Traversal (or Search) for a graph is similar to Depth First Traversal of a tree. The
only catch here is, unlike trees, graphs may contain cycles, so we may come to the same node
again. To avoid processing a node more than once, we use a boolean visited array.
dot co ICET, Mulavoor
For example, in the following graph, we start traversal from vertex 2. When we come to
vertex 0, we look for all adjacent vertices of it. 2 is also an adjacent vertex of 0. If we don‘t
mark visited vertices, then 2 will be processed again and it will become a non-terminating
process. A Depth First Traversal of the following graph is 2, 0, 1, 3.
1) For an unweighted graph, DFS traversal of the graph produces the minimum spanning tree
and all pair shortest path tree.
2) Detecting cycle in a graph
A graph has cycle if and only if we see a back edge during DFS. So we can run DFS for the
graph and check for back edges. (See this for details)
3) Path Finding
We can specialize the DFS algorithm to find a path between two given vertices u and z.
4) Topological Sorting
Topological Sorting is mainly used for scheduling jobs from the given dependencies among
jobs. In computer science, applications of this type arise in instruction scheduling, ordering of
formula cell evaluation when recomputing formula values in spreadsheets, logic synthesis,
determining the order of compilation tasks to perform in makefiles, data serialization, and
resolving symbol dependencies in linkers.
We can augment either BFS or DFS when we first discover a new vertex, color it opposited
its parents, and for each other edge, check it doesn‘t link two vertices of the same color. The
first vertex in any connected component can be red or black! See this for details.
A directed graph is called strongly connected if there is a path from each vertex in the graph
to every other vertex. (See this for DFS based algo for finding Strongly Connected
Components)
7) Solving puzzles with only one solution, such as mazes. (DFS can be adapted to find all
solutions to a maze by only including nodes on the current path in the visited set.)
1) Shortest Path and Minimum Spanning Tree for unweighted graph In unweighted
graph, the shortest path is the path with least number of edges. With Breadth First, we always
reach a vertex from given source using minimum number of edges. Also, in case of
unweighted graphs, any spanning tree is Minimum Spanning Tree and we can use either
Depth or Breadth first traversal for finding a spanning tree.
2) Peer to Peer Networks. In Peer to Peer Networks like BitTorrent, Breadth First Search is
used to find all neighbor nodes.
3) Crawlers in Search Engines: Crawlers build index using Breadth First. The idea is to
start from source page and follow all links from source and keep doing same. Depth First
Traversal can also be used for crawlers, but the advantage with Breadth First Traversal is,
depth or levels of built tree can be limited.
4) Social Networking Websites: In social networks, we can find people within a given
distance ‗k‘ from a person using Breadth First Search till ‗k‘ levels.
5) GPS Navigation systems: Breadth First Search is used to find all neighboring locations.
8) Cycle detection in undirected graph: In undirected graphs, either Breadth First Search or
Depth First Search can be used to detect cycle. In directed graph, only depth first search can
be used.
10) To test if a graph is Bipartite We can either use Breadth First or Depth First Traversal.
dot co ICET, Mulavoor
11) Path Finding We can either use Breadth First or Depth First Traversal to find if there is a
path between two vertices.
12) Finding all nodes within one connected component: We can either use Breadth First or
Depth First Traversal to find all nodes reachable from a given node.
Many algorithms like Prim‘s Minimum Spanning Tree and Dijkstra‘s Single Source Shortest
Pathuse structure similar to Breadth First Search.
SPANNING TREES
A spanning tree is a subset of Graph G, which has all the vertices covered with minimum
possible number of edges. Hence, a spanning tree does not have cycles and it cannot be
disconnected..
By this definition, we can draw a conclusion that every connected and undirected Graph G
has at least one spanning tree. A disconnected graph does not have any spanning tree, as it
cannot be spanned to all its vertices.
We found three spanning trees off one complete graph. A complete undirected graph can
have maximum nn-2 number of spanning trees, where n is the number of nodes. In the above
addressed example, 33−2 = 3 spanning trees are possible.
In a weighted graph, a minimum spanning tree is a spanning tree that has minimum weight
than all other spanning trees of the same graph. In real-world situations, this weight can be
measured as distance, congestion, traffic load or any arbitrary value denoted to the edges.
We shall learn about two most important spanning tree algorithms here −
dot co ICET, Mulavoor
Kruskal's Algorithm
Prim's Algorithm
Both are greedy algorithms.
Dijkstra‘s algorithm is very similar to Prim‘s algorithm for minimum spanning tree. Like
Prim‘s MST, we generate a SPT (shortest path tree) with given source as root. We maintain
two sets, one set contains vertices included in shortest path tree, other set includes vertices
not yet included in shortest path tree. At every step of the algorithm, we find a vertex which
is in the other set (set of not yet included) and has minimum distance from source.
Below are the detailed steps used in Dijkstra‘s algorithm to find the shortest path from a
single source vertex to all other vertices in the given graph.
Algorithm
1) Create a set sptSet (shortest path tree set) that keeps track of vertices included in shortest
path tree, i.e., whose minimum distance from source is calculated and finalized. Initially, this
set is empty.
2) Assign a distance value to all vertices in the input graph. Initialize all distance values as
INFINITE. Assign distance value as 0 for the source vertex so that it is picked first.
a) Pick a vertex u which is not there in sptSetand has minimum distance value.
b) Include u to sptSet.
c) Update distance value of all adjacent vertices of u. To update the distance values,
iterate through all adjacent vertices. For every adjacent vertex v, if sum of distance
value of u (from source) and weight of edge u-v, is less than the distance value of v,
then update the distance value of v.
The set sptSetis initially empty and distances assigned to vertices are {0, INF, INF, INF, INF,
INF, INF, INF} where INF indicates infinite. Now pick the vertex with minimum distance
value. The vertex 0 is picked, include it in sptSet. So sptSet becomes {0}. After including 0
to sptSet, update distance values of its adjacent vertices. Adjacent vertices of 0 are 1 and 7.
The distance values of 1 and 7 are updated as 4 and 8. Following subgraph shows vertices
and their distance values, only the vertices with finite distance values are shown. The vertices
included in SPT are shown in green color.
Pick the vertex with minimum distance value and not already included in SPT (not in
sptSET). The vertex 1 is picked and added to sptSet. So sptSet now becomes {0, 1}. Update
the distance values of adjacent vertices of 1. The distance value of vertex 2 becomes 12.
Pick the vertex with minimum distance value and not already included in SPT (not in
sptSET). Vertex 7 is picked. So sptSet now becomes {0, 1, 7}. Update the distance values of
adjacent vertices of 7. The distance value of vertex 6 and 8 becomes finite (15 and 9
respectively).
dot co ICET, Mulavoor
Pick the vertex with minimum distance value and not already included in SPT (not in
sptSET). Vertex 6 is picked. So sptSet now becomes {0, 1, 7, 6}. Update the distance values
of adjacent vertices of 6. The distance value of vertex 5 and 8 are updated.
We repeat the above steps until sptSet doesn‘t include all vertices of given graph. Finally, we
get the following Shortest Path Tree (SPT).
Topological Sort
An ordering of the vertices in a directed acyclic graph, such that
Types of Graphs:
The graph should be directed: otherwise for any edge (u,v) there would be a path from
u to v and also from v to u, and hence they cannot be ordered.
The graph should be acyclic: otherwise for any two vertices u and v on a cycle u
dot co ICET, Mulavoor
Example :
We can find all strongly connected components in O(V+E) time using Kosaraju‘s algorithm.
1) Create an empty stack ‗S‘ and do DFS traversal of a graph. In DFS traversal, after calling
recursive DFS for adjacent vertices of a vertex, push the vertex to stack. In the above graph,
if we start DFS from vertex 0, we get vertices in stack as 1, 2, 4, 3, 0.
2) Reverse directions of all arcs to obtain the transpose graph.
3) One by one pop a vertex from S while S is not empty. Let the popped vertex be ‗v‘. Take v
as source and do DFS (call DFSUtil(v)). The DFS starting from v prints strongly connected
component of v. In the above example, we process vertices in order 0, 3, 4, 2, 1 (One by one
popped from stack).
The above algorithm is DFS based. It does DFS two times. DFS of a graph produces a single
tree if all vertices are reachable from the DFS starting point. Otherwise DFS produces a
forest. So DFS of a graph with only one SCC always produces a tree. The important point to
note is DFS may produce a tree or a forest when there are more than one SCCs depending
upon the chosen starting point. For example, in the above diagram, if we start DFS from
vertices 0 or 1 or 2, we get a tree as output. And if we start from 3 or 4, we get a forest. To
find and print all SCCs, we would want to start DFS from vertex 4 (which is a sink vertex),
then move to 3 which is sink in the remaining set (set excluding 4) and finally any of the
remaining vertices (0, 1, 2). So how do we find this sequence of picking vertices as starting
points of DFS? Unfortunately, there is no direct way for getting this sequence. However, if
we do a DFS of graph and store vertices according to their finish times, we make sure that the
finish time of a vertex that connects to other SCCs (other that its own SCC), will always be
greater than finish time of vertices in the other SCC (See this for proof). For example, in DFS
of above example graph, finish time of 0 is always greater than 3 and 4 (irrespective of the
sequence of vertices considered for DFS). And finish time of 3 is always greater than 4. DFS
doesn‘t guarantee about other vertices, for example finish times of 1 and 2 may be smaller or
greater than 3 and 4 depending upon the sequence of vertices considered for DFS. So to use
this property, we do DFS traversal of complete graph and push every finished vertex to a
stack. In stack, 3 always appears after 4, and 0 appear after both 3 and 4.
In the next step, we reverse the graph. Consider the graph of SCCs. In the reversed graph, the
edges that connect two components are reversed. So the SCC {0, 1, 2} becomes sink and the
SCC {4} becomes source. As discussed above, in stack, we always have 0 before 3 and 4. So
if we do a DFS of the reversed graph using sequence of vertices in stack, we process vertices
from sink to source (in reversed graph). That is what we wanted to achieve and that is all
needed to print SCCs one by one.
dot co ICET, Mulavoor
MODULE IV
Steps
Splits the input with size n into k distinct sub problems 1 < k ≤ n
Solve sub problems
Combine sub problem solutions to get solution of the whole problem. Often sub
problems will be of same type as main problem. Hence solutions can be expressed
as recursive algorithms
Control Abstraction
dot co ICET, Mulavoor
DAndC (P)
else
Merge Sort
Given a sequence of n elements a[1],...,a[n].
Each set is individually sorted, resultant is merged to form sorted list of n elements
Algorithm
dot co ICET, Mulavoor
Example
Consider 10 elements 310, 285, 179, 652, 351, 423, 861, 254, 450 and 520. The recursive
call can be represented using a tree as given below
dot co ICET, Mulavoor
= 4T(n/4) + 2cn
=…
= 2kT(1) + kcn
T(n) = an + cnlogn
If 2k<n≤2k+1 , T(n)≤T(2k+1)
The product of two n x n matrices X and Y is a third , n x n matrix Z=XY , with (i,j)th entry
Zij=∑Xik Ykj
k=1
In general XY, not the same as YX; matrix multiplication is not commutative.
The formula above implies an O (n3) algorithm for matrix multiplication: there are n2 entries
to be computed, and each takes linear time. For quite a while, this was widely believed to be
the best running time possible, and it was even proved that no algorithm which used just
additions and multiplications could do better. It was therefore a source of great excitement
dot co ICET, Mulavoor
when in 1969, Strassen announced a signi_cantly more ef_cient algorithm, based upon
divide-and-conquer.
Matrix multiplication is particularly easy to break into sub problems, because it can be
performed blockwise. To see what this means, carve X into four n/2 x n/2 blocks, and also Y:
A B E F
X= Y=
C D G H
Then their product can be expressed in terms of these blocks, and is exactly as if the blocks
were single elements.
A B E F AE+BG AF+BH
XY=
C D G H CE+DG CF+DH
which comes out to O(n3), the same as for the default algorithm. However, an improvement
in the time bound is possible, and as with integer multiplication, it relies upon algebraic
tricks. It turns out that XY can be computed from just seven sub problems.
P1 = A(F – H)
=
P2 (A + B)H
=
P3 (C + D)E
dot co ICET, Mulavoor
=
P4 D(G – E)
=
P5 (A + D) (E + H)
=
P6 (B - D) (G + H)
=
P7 (A - C) (E + F)
P +P – P +P P +P
XY = 5 4 2 6 1 2
P +P P +P – P – P
3 4 1 5 3 7
Strassen showed that 2x2 matrix multiplication can be accomplished in 7 multiplication and
18 additions or subtractions. This reduce can be done by Divide and Conquer
Approach.Divide the input data S in two or more disjoint subsets S1, S2. Solve the
subproblems recursively. Combine the solutions for S1, S2, …, into a solution for S. The base
case for the recursion are subproblems of constant size.Analysis can be done using recurrence
equations.Divide matrices in sub-matrices and recursively multiply sub-matrices
Let A, B be two square matrices over a ring R. We want to calculate the matrix product C as
If the matrices A, B are not of type 2n x 2n we fill the missing rows and columns with zeros.
with
then
With this construction we have not reduced the number of multiplications. We still need 8
multiplications to calculate the Ci,j matrices, the same number of multiplications we need
when using standard matrix multiplication.
which are then used to express the Ci,j in terms of Mk. Because of our definition of the Mk we
dot co ICET, Mulavoor
can eliminate one matrix multiplication and reduce the number of multiplications to 7 (one
multiplication for each Mk) and express the Ci,j as
We iterate this division process n-times until the submatrices degenerate into numbers (group
elements).
Numerical analysis
The standard matrix multiplications takes approximately 2n3 arithmetic operations (additions
and multiplications); the asymptotic complexity is O(n3). (Refer Note for analysis)
C i, j a i ,k
bk , j
k 1
N N N
Thus T ( N ) c cN O(N )
3 3
i 1 j 1 k 1
The number of additions and multiplications required in the Strassen algorithm can be
calculated as follows: let f(k) be the number of operations for a matrix. Then by
recursive application of the Strassen algorithm, we see that f(k) = 7f(k − 1) + l4k, for some
constant l that depends on the number of additions performed at each application of the
algorithm. Hence f(k) = (7 + o(1))k, i.e., the asymptotic complexity for multiplying matrices
dot co ICET, Mulavoor
The reduction in the number of arithmetic operations however comes at the price of a
somewhat reduced numerical stability.
Algorithm
if (n == 1)
else
matmul(A, B, R, n/4);
}
dot co ICET, Mulavoor
DYNAMIC PROGRAMMING
It is used when the solution to a problem can be viewed as the result of a sequence of
decisions. It avoid duplicate calculation in many cases by keeping a table of known results
and fills up as sub instances are solved.
It follows a bottom-up technique by which it start with smaller and hence simplest sub
instances. Combine their solutions to get answer to sub instances of bigger size until we
arrive at solution for original instance.
Dynamic programming differs from Greedy method because Greedy method makes only one
decision sequence but dynamic programming makes more than one decision.
The idea of dynamic programming is thus quit simple: avoid calculating the same thing
twice, usually by keeping a table of known result that fills up a sub instances are solved.
When a problem is solved by divide and conquer, we immediately attack the complete
instance, which we then divide into smaller and smaller sub-instances as the algorithm
progresses.
We usually start with the smallest and hence the simplest sub- instances.
The essential difference between the greedy method and dynamic programming is that
the greedy method only one decision sequence is ever generated.
dot co ICET, Mulavoor
RELATED NOTES:
once and then saves its answer in a table, thereby avoiding the work of re-computing the
answer every time.
Two main properties of a problem suggest that the given problem can be solved using
Overlapping Sub-Problems
sub-problems. It is mainly used where the solution of one sub-problem is needed repeatedly.
The computed solutions are stored in a table, so that these don‘t have to be re-computed.
Hence, this technique is needed where overlapping sub-problem exists.
For example, Binary Search does not have overlapping sub-problem. Whereas recursive
program of Fibonacci numbers have many overlapping sub-problems.
Optimal Sub-Structure
A given problem has Optimal Substructure Property, if the optimal solution of the given
problem can be obtained using optimal solutions of its sub-problems.
For example, the Shortest Path problem has the following optimal substructure property −
If a node x lies in the shortest path from a source node u to destination node v, then the
shortest path from u to v is the combination of the shortest path from u to x, and the shortest
path from x to v.
dot co ICET, Mulavoor
The standard All Pair Shortest Path algorithms like Floyd-Warshall and Bellman-Ford are
typical examples of Dynamic Programming.
***************************************************************
Principle of Optimality
An optimal sequence of decisions has the property that whatever the initial state and
decisions are, the remaining decisions must constitute an optimal decision sequence with
regard to the state resulting from the first decision.
MatrixChain Multiplication
Given a sequence of matrices, find the most efficient way to multiply these matrices
together. The problem is not actually to perform the multiplications, but merely to decide in
which order to perform the multiplications.
We have many options to multiply a chain of matrices because matrix multiplication is
associative. In other words, no matter how we parenthesize the product, the result will be the
same. For example, if we had four matrices A, B, C, and D, we would have:
However, the order in which we parenthesize the product affects the number of simple
dot co ICET, Mulavoor
arithmetic operations needed to compute the product, or the efficiency. For example, suppose
A is a 10 × 30 matrix, B is a 30 × 5 matrix, and C is a 5 × 60 matrix. Then,
There are 4 matrices of dimensions 10x20, 20x30, 30x40 and 40x30. Let the input 4
matrices be A, B, C and D. The minimum number of multiplications are obtained by putting
parenthesis in following way
1) Optimal Substructure:
A simple solution is to place parenthesis at all possible places, calculate the cost for each
placement and return the minimum value. In a chain of matrices of size n, we can place the
first set of parenthesis in n-1 ways. For example, if the given chain is of 4 matrices. let the
chain be ABCD, then there are 3 ways to place first set of parenthesis outer side: (A)(BCD),
dot co ICET, Mulavoor
(AB)(CD) and (ABC)(D). So when we place a set of parenthesis, we divide the problem into
subproblems of smaller size. Therefore, the problem has optimal substructure property and
can be easily solved using recursion.
Minimum number of multiplication needed to multiply a chain of size n = Minimum of all n-
1 placements (these placements create subproblems of smaller size)
2) Overlapping Subproblems
Following is a recursive implementation that simply follows the above optimal substructure
property.
getchar();
return 0;
}
Time complexity of the above naive recursive approach is exponential. It should be noted that
the above function computes the same subproblems again and again. See the following
recursion tree for a matrix chain of size 4. The function MatrixChainOrder (p, 3, 4) is called
two times. We can see that there are many subproblems being called more than once.
Since same suproblems are called again, this problem has Overlapping Subprolems property.
So Matrix Chain Multiplication problem has both properties (see this and this) of a dynamic
programming problem. Like other typical Dynamic Programming(DP) problems,
Following is C/C++ implementation for Matrix Chain Multiplication problem using Dynamic
Programming.
int i, j, k, L, q;
// L is chain length.
for (L=2; L<n; L++)
{
for (i=1; i<n-L+1; i++)
{
j = i+L-1;
m[i][j] = INT_MAX;
for (k=i; k<=j-1; k++)
{
// q = cost/scalar multiplications
q = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];
if (q < m[i][j])
m[i][j] = q;
}
}
}
return m[1][n-1];
int main()
{
int arr[] = {1, 2, 3, 4};
int size = sizeof(arr)/sizeof(arr[0]);
getchar();
return 0;
}
Output:
Bellman–Ford Algorithm
Given a graph and a source vertex src in graph, find shortest paths from src to all vertices in
the given graph. The graph may contain negative weight edges.
We have discussed Dijkstra‘s algorithm for this problem. Dijksra‘s algorithm is a Greedy
algorithm and time complexity is O(VLogV) (with the use of Fibonacci heap). Dijkstra
doesn’t work for Graphs with negative weight edges, Bellman-Ford works for such graphs.
Bellman-Ford is also simpler than Dijkstra and suites well for distributed systems. But time
complexity of Bellman-Ford is O(VE), which is more than Dijkstra.
Algorithm
Output: Shortest distance to all vertices from src. If there is a negative weight cycle, then
shortest distances are not calculated, negative weight cycle is reported.
1) This step initializes distances from source to all vertices as infinite and distance to source
itself as 0. Create an array dist[] of size |V| with all values as infinite except dist[src] where
src is source vertex.
2) This step calculates shortest distances. Do following |V|-1 times where |V| is the number of
vertices in given graph.
3) This step reports if there is a negative weight cycle in graph. Do following for each edge u-
v
……If dist[v] > dist[u] + weight of edge uv, then ―Graph contains negative weight cycle‖
The idea of step 3 is, step 2 guarantees shortest distances if graph doesn‘t contain negative
dot co ICET, Mulavoor
weight cycle. If we iterate through all edges one more time and get a shorter path for any
vertex, then there is a negative weight cycle
How does this work? Like other Dynamic Programming Problems, the algorithms calculate
shortest paths in bottom-up manner. It first calculates the shortest distances which have at-
most one edge in the path. Then, it calculates shortest paths with at-nost 2 edges, and so on.
After the i-th iteration of outer loop, the shortest paths with at most i edges are calculated.
There can be maximum |V| – 1 edge in any simple path that is why the outer loop runs |v| – 1
times. The idea is, assuming that there is no negative weight cycle, if we have calculated
shortest paths with at most i edges, then an iteration over all edges guarantees to give shortest
path with at-most (i+1) edges.
Example
Let us understand the algorithm with following example graph. The images are taken
from this source.
Let the given source vertex be 0. Initialize all distances as infinite, except the distance to
source itself. Total number of vertices in the graph is 5, so all edges must be processed 4
times.
Let all edges are processed in following order: (B,E), (D,B), (B,D), (A,B), (A,C), (D,C),
(B,C), (E,D). We get following distances when all edges are processed first time. The first
row in shows initial distances. The second row shows distances when edges (B,E), (D,B),
(B,D) and (A,B) are processed. The third row shows distances when (A,C) is processed. The
fourth row shows when (D,C), (B,C) and (E,D) are processed.
dot co ICET, Mulavoor
The first iteration guarantees to give all shortest paths which are at most 1 edge long. We get
following distances when all edges are processed second time (The last row shows final
values).
The second iteration guarantees to give all shortest paths which are at most 2 edges long. The
algorithm processes all edges 2 more times. The distances are minimized after the second
iteration, so third and fourth iterations don‘t update the distances.
Bellman-Ford makes IEI relaxations for every iteration, and there are IVI- 1 iterations.
Therefore, the worst-case scenario is that Bellman-Ford runs in O(IVI.IEI) time.
};
printArr(dist, V);
return;
}
graph->edge[0].src = 0;
graph->edge[0].dest = 1;
graph->edge[0].weight = -1;
graph->edge[4].dest = 4;
graph->edge[4].weight = 2;
BellmanFord(graph, 0);
return 0;
}
Output:
0 0
1 -1
2 2
3 -2
4 1
dot co ICET, Mulavoor
Notes
1) Negative weights are found in various applications of graphs. For example, instead of
paying cost for a path, we may get some advantage if we follow the path.
2) Bellman-Ford works better (better than Dijksra‘s) for distributed systems. Unlike Dijksra‘s
where we need to find minimum value of all vertices, in Bellman-Ford, edges are considered
one by one.
MODULE V
ANALYSIS
1. The divide-and-conquer paradigm involves three steps at each level of the recursion:
2. They call themselves recursively one or more times to deal with closely related sub
problems.
3. D&C does more work on the sub-problems and hence has more time consumption.
dot co ICET, Mulavoor
Dynamic Programming
3. DP solves the sub problems only once and then stores it in the table.
GREEDY STRATEGY
Greedy method is another important algorithm design paradigm. Before going in detail
about the method let us see what an optimization problem is about.
function. Solutions that satisfy the constraints are called feasible solutions. And feasible
solution for which the optimization function has the best possible value is called an
optimal solution.
Control Abstraction
Type x = Select(a);
If Feasible(solution, x)
return solution;
Select – select an input from a[] and removes it from the array
Knapsack problem
Problem: Given n inputs and a knapsack or bag. Each object i is associated with a weight wi
and profit pi. If fraction of an object xi, 0 ≤ xi ≥ 1 is placed in knapsack earns profit pixi. Fill
the knapsack with maximum profit.
dot co ICET, Mulavoor
Example
Number of inputs n = 3
(0, 2/3, 1) 20 31
Algorithm
// p[i]/w[i] ≥ p[i+1]/w[i+1]
x[i] = 0.0;
float u = m;
x[i] = 1.0;
dot co ICET, Mulavoor
u = u – w[i];
Theorem 3.1
If objects are selected in order of decreasing pi/wi, then knapsack finds an optimal
solution
A Minimum cost spanning tree is a subset T of edges of G such that all the vertices
remain connected when only edges in T are used, and sum of the lengths of the edges in T
is as small as possible. Hence it is then a spanning tree with weight less than or equal to
the weight of every other spanning tree.
We know that the number of edges needed to connect an undirected graph with n vertices
is n-1. If more that n-1 edges are present, then there will be a cycle. Then we can remove
an edge which is a part of the cycle without disconnecting T. This will reduce the cost.
There are two algorithms to find minimum spanning trees. They are Prim‘s algorithm and
Kruskal‘s algorithm.
1. Prim’s Algorithm
Prim's algorithm finds a minimum spanning tree for a connected weighted graph. This
means it finds a subset of the edges that forms a tree that includes every vertex, where the
total weight of all the edges in the tree is minimized. The algorithm was discovered in
1930 by mathematician Vojtěch Jarník and later independently by computer scientist
Robert C. Prim in 1957 and rediscovered by Edsger Dijkstra in 1959. Therefore it is
sometimes called the DJP algorithm, the Jarník algorithm, or the Prim-Jarník algorithm.
dot co ICET, Mulavoor
Steps
Example 3.2
Minimum spanning tree using Prim‘s algorithm can be formed as given below.
int near[], j, k, l, I;
dot co ICET, Mulavoor
t[1][1] = k;
t[1][2] = l;
else near[i] = k;
near[k] = 0;
near[l] = 0;
t[i][1] = j;
t[i][2] = near[j];
near[j] = 0;
near[k] = j;
return (mincost);
2. Kruskal’s Algorithm
Kruskal's algorithm is another algorithm that finds a minimum spanning tree for a
connected weighted graph. If the graph is not connected, then it finds a minimum
spanning forest (a minimum spanning tree for each connected component).
dot co ICET, Mulavoor
Kruskal's Algorithm builds the MST in forest. Initially, each vertex is in its own tree in
forest. Then, algorithm considers each edge in turn, order by increasing weight. If an edge
(u, v) connects two different trees, then (u, v) is added to the set of edges of the MST, and
two trees connected by an edge (u, v) are merged into a single tree on the other hand, if an
edge (u, v) connects two vertices in the same tree, then edge (u, v) is discarded. The
resultant may not be a tree in all stages. But can be completed into a tree at the end.
t = EMPTY;
while ((t has fewer than n-1 edges) && (E != EMPTY))
{
choose an edge(v, w) from E of lowest cost;
delete (v, w) from E;
if (v, w) does not create a cycle in t
add (v, w) to t;
else
discard (v, w);
}
To check whether there exist a cycle, place all vertices in the same connected component
of t into a set. Then two vertices v and w are connected in t then they are in the same set.
Example
Minimum spanning tree using Kruskal‘s algorithm can be formed as given below.
dot co ICET, Mulavoor
Union(j, k);
}
if (i != n-1) printf(―No spanning tree \n‖);
else return(mincost);
}
}
MODULE VI
BACK TRACKING
Backtracking find all answer nodes, not just one case. Let (x1, ..., xi) be a path from root to a
node in the state space tree. T(x1, ..., xi) be the set of all possible values for xi+1 such that
(x1, ..., xi,xi+1) is also a path to problem state. So we know that T(x1, ..., xn) = φ. Let Bi+1
be a bounding function(criterion function) such that if Bi+1(x1, ..., xi,xi+1) is false for the
path (x1, ...,xi,xi+1) from the root to the problem state, then path cannot be extended to
answer node.Then candidates for position i+1 are those generated by the T satisfying Bi+1.
Control Abstraction
// While entering assume that first k-1 values x[1], x[2],…,x[k-1] of the
solution
// vector x[1:n] have been assigned.
for (each x[k] such that x[k] ∈ T(x[1],…,x[k-1])) do
dot co ICET, Mulavoor
{
if (Bk(x[1],…,x[k-1]!=0)) then
{
if ((x[1],…,x[k]) is a path to an answer node)
write x[1:k];
if (k<n) Backtrack(k+1);
}
}
}
∗ Only applicable to problmes which admit the concept of partial candidate solution
and a relatively quick test of whether the partial solution can grow into a complete solution
∗ If a problem does not satisfy the above constraint, backtracking is not applicable·
Backtracking is not very efficient to find a given value in an unordered list
All the solutions require a set of constraints divided into two categories:
Definition 1: Explicit constraints are rules that restrict each xi to take on values only from a
given set.
– Explicit constraints depend on the particular instance I of problem being
solved
– All tuples that satisfy the explicit constraints define a possible solution space
for I.
Definition 2: Implicit constraints are rules that determine which of the tuples in the solution
space of I satisfy the criterion function.
– Implicit constraints describe the way in which the xi‘s must relate to each
other..
dot co ICET, Mulavoor
Determine problem solution by systematically searching the solution space for the given
problem instance
N Queens Problem
Problem: Given an nxn chessboard, place n queens in nom-attacking position i.e., no two
queens are in same row or same row or same diagonal. Let us assume that queen i is placed in
row i. So the problem can be represented as n tuple (x1, ..., xn) where each xi represents the
column in which we have to place queen i.Hence the explicit constraints is Si ={1,2,..,n}. No
two xi can be same and no two queens can be in same diagonal is the implicit constraint.
Since we fixed the row number the solution space reduce from nn to n!. To check whether
they are on the same diagobal, let chessboard be represented by an array a[1..n][1..n]. Every
element with same diagonal that runs from upper left to lower right has same ―row-column‖
value. E.g., consider the
element a[4][2]. Elements a[3][1], a[5][3], a[6][4], a[7][5] and a[8][6] have row-column
value 2. Similarly every element from same diagonal that goes from upper right to lower left
has same ―row+column‖ value. Tthe elements a[1][5], a[2][4], a[3][3], a[5][1] have same
―row+column‖ value as that of element a[4][2] which is 6.
Hence two queens placed at (i,j) and (k,l) have same diagonal iff
i – j = k – l or i + j = k + l
i.e., j – l = i – k or j – l = k – i
|j – l| = |i – k|
The n-queens problem is a generalization of the 8-queens problem. Here n queens are to be
placed on an nxn chessboard so that no two attack, that is no two queens are on the same row,
column, or diagonal. The solution space consists of all n! permutations of the n-tuple
(1,2,…,n). The tree is called permutation tree. The edges are labeled by possible values of xi.
Edges from level 1 to level 2 nodes specify the values for x1. Edges from level i to i+1 are
labeled with values of xi. The solution space is defined by all the paths from the root node to
a leaf node. For eg. If n=4, then there will be 4! =24 leaf nodes in the tree.
―attack,‖ that is, so that no two of them are on the same row, column, or diagonal.
∗ Second pass: Since each queen is in a different row, define the chessboard solution to be an
8-tuple (x1, . . . , x8), where xi is the column for ith queen
∗ No two xi can be the same, or all the queens must be in different columns
Terminology
Problem state: is each node in the depth-first search tree
State space: is the set of all paths from root node to other nodes
Solution states: are the problem states s for which the path from the root node to s
defines a tuple in the solution space.
–In variable tuple size formulation tree, all nodes are solution states
–In fixed tuple size formulation tree, only the leaf nodes are solution states
–Partitioned into disjoint sub-solution spaces at each internal node
Answer states: are those solution states s for which the path from root node to s defines a
tuple that is a member of the set of solutions.
Static trees: are ones for which tree organizations are independent of the problem instance
dot co ICET, Mulavoor
being solved
–Fixed tuple size formulation
–Tree organization is independent of the problem instance being solved
Dynamic trees: are ones for which organization is dependent on problem instance
–After conceiving state space tree for any problem, the problem can be solved by
systematically generating problem states, checking which of them are solution states, and
checking which solution states are answer states.
Live node: is a generated node for which all of the children have not been generated yet.
E-node: is a live node whose children are currently being generated or explored
• Bounding function
TREE STRUCTURE
Tree organization of the 4-queens solution space. Nodes are numbered as in depth first
search.
The n-queens problem is a generalization of the 8-queens problem. Now n queens are to be
placed on an n×n chess board so that no two attack; that is, no two queens are on the same
row, column or diagonal.Generalising our discussions, the solution space consists of all n!
permutations of n-tuple (1, 2.., n).The above figure shows a possible tree organization for the
case n=4. A tree such as this is called a permutation tree. The edges are labeled by possible
values of xi. Edges from level 1 to level 2 nodes specify the value for x1.Thus the leftmost
sub tree contains all solutions with x1=1 and x2=2,and so on. Edges from level to level
i+1are labeled with the value of xi.the solution space is defined by all paths from the root
node to a leaf node. There are 4! =24 nodes in the tree.
ALGORITHM
Algorithm Place (k, ί)
dot co ICET, Mulavoor
In this tutorial, earlier we have discussed Fractional Knapsack problem using Greedy
approach. We have shown that Greedy approach gives an optimal solution for Fractional
Knapsack.
In 0-1 Knapsack, items cannot be broken which means the thief should take the item as
a whole or should leave it. This is reason behind calling it as 0-1 Knapsack.
Hence, in case of 0-1 Knapsack, the value of xi can be either 0 or 1, where other constraints
remain the same.
0-1 Knapsack cannot be solved by Greedy approach. Greedy approach does not ensure an
optimal solution. In many instances, Greedy approach may give an optimal solution.
Introduction
Branch and bound is another algorithm technique that we are going to present in our multi-
part article series covering algorithm design patterns and techniques. B&B, as it is often
abbreviated, is one of the most complex techniques and surely cannot be discussed in its
entirety in a single article. Thus, we are going to focus on the so-called A* algorithm that is
the most distinctive B&B graph search algorithm.
If you have followed this article series then you know that we have already covered the most
important techniques such as backtracking, the greedy strategy, divide and conquer, dynamic
programming, and even genetic programming. As a result, in this part we will compare
branch and bound with the previously mentioned techniques as well. It is really useful to
understand the differences.
Branch and bound is an algorithm technique that is often implemented for finding the optimal
solutions in case of optimization problems; it is mainly used for combinatorial and discrete
global optimizations of problems. In a nutshell, we opt for this technique when the domain of
possible candidates is way too large and all of the other algorithms fail. This technique is
based on the en masse elimination of the candidates.
You should already be familiar with the tree structure of algorithms. Out of the techniques
that we have learned both the backtracking and divide and conquer traverse the tree in its
depth, though they take opposite routes. The greedy strategy picks a single route and forgets
about the rest. Dynamic programming approaches this in a sort of breadth-first search
variation (BFS).
Now if the decision tree of the problem that we are planning to solve has practically
unlimited depth, then, by definition, the backtracking and divide and conquer algorithms are
out. We shouldn't rely on greedy because that is problem-dependent and never promises to
dot co ICET, Mulavoor
As our last resort we may even think about dynamic programming. The truth is that maybe
the problem can indeed be solved with dynamic programming, but the implementation
wouldn't be an efficient approach; additionally, it would be very hard to implement. You see,
if we have a complex problem where we would need lots of parameters to describe the
solutions of sub-problems, DP becomes inefficient.
Branch and bound is a systematic method for solving optimization problems B&B is a rather
general optimization technique that applies where the greedy method and dynamic
programming fail. However, it is much slower. Indeed, it often leads to exponential time
complexities in the worst case. On the other hand, if applied carefully, it can lead to
algorithms that run reasonably fast on average. The general idea of B&B is a BFS-like search
for the optimal solution, but not all nodes get expanded (i.e., their children generated).
Rather, a carefully selected criterion determines which node to expand and when, and another
criterion tells the algorithm when an optimal solution has been found. The basic concept
underlying the branch-and-bound technique is to divide and conquer. Since the original
―large‖ problem is hard to solve directly,it is divided into smaller and smaller subproblems
until these subproblems can be conquered. The dividing (branching) is done by partitioning
the entire set of feasible solutions into smaller and smaller subsets.The conquering
(fathoming) is done partially by (i) giving a bound for the best solution in the subset;(ii)
discarding the subset if the bound indicates that it can‘t contain an optimal solution. These
three basic steps – branching, bounding, and fathoming – are illustrated on the following
example.
The problem an be represented using a graph. Let G=(V,E) be a directed graph with cost cij,
cij >0 for all <i j> E and cij = ∞ for all <j,t> E. |V| = n and n>1. We know that tour of a
graph includes all vertices in V and cost of tour is the sum of the cost of all edges on the tour.
Hence the traveling salesman problem is to minimize the cost.
Application
dot co ICET, Mulavoor
The traveling salesman problem can be correlated to many problems that we find in the day
to day life. For example, consider a production environment with many commodities
manufactured by same set of machines. Manufacturing occur in cycles. In each production
cycle n different commodities are produced. When machine changes from product i to
product j, a cost Cij is incurred. Since products are manufactured cyclically, for the change
from last commodity to the first a cost is incurred. The problem is to find the optimal
sequence to manufacture the products so that the production cost is minimum.
We know that the tour of the simple graph starts and ends at vertex1. Every tour consist of an
edge <i, k> for some k V-{1} and a path from k to 1. Path from k to 1 goes through each
vertex in V- {1,k} exactly once. If tour is optimal, path from k to 1 muat be shortest k to 1
path going through all vertices in V-{1,k}. Hence the principle of optimality holds.
Let g(i, S) be length of shortest path starting at i, going through all vertices in S ending at 1. Function
g(1, V-{1}) is the optimal salesman tour. From the principle of optimality
Use eq.(1) to get g(i, S) for |S| =1. Then find g(i, S) with |S|=2 and so on.
Example
Consider the directed graph with the cost adjacency matrix given below.
g(2, φ) = C21 = 5
dot co ICET, Mulavoor
g(3, φ) = C31 = 6
g(4, φ) = C41 = 8
g(1, {2,3,4}) = min{C12 + g(2, {3,4}) , C13 + g(3, {2,4}) , C14 + g(4, {2,3}) }
Let J(i, S) represent the value of j which is the value of g(i, S) that minimizes the right
hand side of equ(2). Then J(1, {2,3,4}) =2, which infers that the tour starts from 1
goes to 2. Since J(2,{3,4}) = 4, we know that from 2 it goes to 4. J (4, {3}) = 3. Hence
the next vertex is 3. Then we have the optimal sequence as 1, 2,4,3,1.