Dynamic Programming (Ddpanda)
Dynamic Programming (Ddpanda)
of Algorithms
For
Computer Science
&
Information Technology
By
[Link]
✆080-40611000
Syllabus
Searching, Sorting, Hashing, Asymptotic Worst Case Time and Space Complexity, Algorithm Design
Techniques, Greedy, Dynamic Programming and Divide‐and‐Conquer, Graph Search, Minimum
Spanning Trees, Shortest Paths.
[Link]/gate-papers
[Link]/gate-syllabus
Contents
#5. Hashing 66 – 70
Hashing 66 – 67
Floading 67
Conflict Resolution Techniques 67 – 68
Double Hashing 68 – 69
Separate Chaining 69
Coalesced Chaining 70
Reference Books 71
11
…Goethe
CHAPTER
Algorithm Analysis
Learning Objectives
After reading this chapter, you will know:
1. Definition of Algorithm
2. Need for Analysis
3. Algorithm Analysis
4. Asymptotic Notation
5. Recurrence
Introduction
Once an algorithm is given for a problem and decided to be correct, then an important step is to
determine how much in the way of resources, such as time or space, the algorithm will be required.
The analysis required to estimate use of these resources of an algorithm is generally a theoretical
issue and therefore a formal framework is required. In this framework, we shall consider a normal
computer as a model of computation that will have the standard repertoire of simple instructions
like addition, multiplication, comparison and assignment, but unlike the case with real computer, it
takes exactly one unit time to do anything (simple) and there are no fancy operations such as matrix
inversion or sorting, that clearly cannot be done in one unit time. We always assume infinite
memory also.
Definition of Algorithm
An Algorithm is a finite sequence of instructions that, if followed, accomplishes a particular task. In
addition, all algorithms must satisfy the following criteria:
1. Input: Zero or more quantities are externally supplied.
2. Output: At least one quantity is produced.
3. Definiteness: Each instruction is clear and unambiguous.
4. Finiteness: If trace out the instructions of an algorithm, then for all cases, the algorithm
terminates after a finite number of steps.
5. Effectiveness: Every instruction must be very basic so that it can be carried out, in principle, by
a person using only pencil and paper. It is not enough that each operation be definite as in
criteria 3; it also must be feasible.
(ii) Design: For each object there will be some basic operations to perform on it. These operations
already exist in the form of procedures and write an algorithm which solves the problem
according to the requirements.
(iii) Analysis: Can we think of another algorithm? If so, write it down. Next, try to compare these two
methods. It may already be possible to tell if one will be more desirable than the other. If you
can’t distinguish between the two, choose one to work on for now and we will return to the
second version later.
(iv) Refinement and Coding: Modern approach suggests that all processing which is independent of
the data representation be written out first.
(v) Verification: Verification consists of three distinct aspects;
(1) Program Proving, (2) Testing and (3) Debugging
Algorithm Analysis
Types of Analysis/Behavior of Algorithm
Worst Case
Provides an upper bound on running time
An absolute guarantee that the algorithm would not run longer, no matter what the inputs are
Best Case
Provides a lower bound on running time
Input is the one for which the algorithm runs the fastest
Average Case
Provides a prediction about running time
Assumes that the input is random
ower ound unning Time pper ound
The following two components need to be analyzed for determining algorithm efficiency. If we have
more than one algorithms for solving a problem then we really need to consider these two before
utilizing one of them.
Time Complexity
The time complexity of an algorithm is to find the time taken by an Algorithm to complete its
execution. There two methods
1. A Priori Analysis: It is based on determining the order of the magnitude of the statement,
construct or data structure. This method is independent of machine, programming language
and operating system. If algorithm has to be analyzed further in detail then each operation
(Arithmetic, Relational and Logical) is considered to take 1 unit of time.
2. Posteriori Testing: In this method an algorithm is converted into a program using any
programming language, executed on a particular machine. Algorithm’s execution time is taken
with the systems watch time. Result of this analysis will be dependent on the machine and
language used. It will be real time.
Running Time Complexity: The time required for running an algorithm.
Space Complexity
The amount of space required at run-time by an algorithm for solving a given problem.
In general these measurements are expressed in terms of asymptotic notations, like Big-Oh, Theta,
Omega etc.
Example: Consider the simple program fragment for analyzing space complexity.
int sum(int n)
{
int partialSum = 0;
for(int i = 0; i<n; i++)
partialSum = partialSum i i i;
return partialSum;
}
Notice that the memory used by this program is absolutely independent of input size
because this is non-recursive program and has only one activation record to be pushed on
the system stack whose size is not going to change with n and also each invocation
doesn’t have any heap space requirement which is dependent on n. So whether n = 10,
20, 100, etc, the number of records to be pushed is O (1). Therefore Space Complexity is O
(1).
Example: Consider the recursive C program which prints the null terminated string in the reverse
order.
void printRev(char *str)
{
if( *str == ‘\0’) return;
printRev(str+1);
printf(“%c”,*str);
}
Maximum width of the system stack is O(L) where L is string length. And the size of
activation record is constant w.r.t length L. Therefore, space complexity is O(L).
Example: Consider the simple program fragment whose running time cost is O(n) where n is a
positive integer.
int sum(int n)
{
int partialSum = 0;
for (int i = 1; i<= n; i++)
partialSum = partialSum i i i;
return partialSum;
}
Lines 1 and 4 count one unit each. Line 3 counts for four units per time executed
(two multiplications, one addition, and one assignment) and is executed n times, for a
total of 4n units. Line 2 has hidden costs of initializing i, testing i <= n and incrementing i.
The total cost of all these is 1 to initialize, n+1 for all the tests, and n for all the
increments, which 2n+[Link], total cost of 6n+4, which is O(n).
As an example, the following program fragment, which has O(n) work followed by O(n2)
work, is also O(n2).
for( i = 0; i< n ; i++)
k++;
for( i = 0; i< n ; i++)
for( j = 0; j<n; j++)
k++;
Rule 4 – if/else
For the fragment
if (condition )
S1
else
S2
Running time of an if/else statement is never more than the running time of test plus the
larger of the running times of S1 and S2.
Example: Consider the code given below for printing null terminated string in reverse order.
Let T (n) be the running time of printRev ( ) for which string length is n. Then, T(n )
would represent running time of the same function which is given string of length n – 1.
Thus
T(n) = T(n 1) + O(1)
At first look this algorithm gives the illusion of O (n) cost as we might think that since it has a while
loop and that will always run for the entire length of the given array. A careful examination would
expose that the loop will not run more than O (logn) times since in each iteration high or low getting
adjusted such that the problem size decreases by half of the current size. The following recurrence
relation can best represent the running time of binary search algorithm. T (n) = T (n/2) + 1, where
T (n) be the running time for n input elements.
Look at another interesting code given below.
sum = 0;
for( i = 1; i< n ; i)
sum++;
The i variable values getting incremented in each iteration such that its becoming double of current
hence certainly would not take much longer than logn to reach its value as n or more.
Amortized Analysis
An amortized analysis is any strategy for analyzing a sequence of operations to show that the
average cost per operation is small, even though a single operation within the sequence might be
expensive. Even though we take averages, however, probability is not involved. An amortized
analysis guarantees the average performance of each operation in the worst case.
Online Algorithm
Definition: An algorithm that must process each input in turn, without detailed knowledge of future
inputs. In computer science, an online algorithm is one that can process its input piece-by-piece in a
serial fashion, i.e., in the order that the input is fed to the algorithm without having the entire input
available from the start. In contrast, an offline algorithm is given the whole problem data from the
beginning and is required to output an answer which solves the problem at hand (for example,
selection sort requires that the entire list be given before if can sort it, while insertion sort doesn’t).
Since it does not know the whole input, an online algorithm is forced to make decisions that may
later turn out not be optimal, and the study of online algorithms has focused on the quality of
decision-making that is possible in this setting. Competitive analysis formalizes this idea by
comparing the relative performance of an online and offline algorithm for the same problem
instance.
A problem exemplifying the concepts of online algorithms is the Canadian Traveler Problem, The
goal of this problem is to minimize the cost reaching a target in weighted graph where some of the
edges are unreliable and may have been removed from the graph. However, that an edge has been
removed (failed) is only revealed to the traveler when she/he reaches one of the edges endpoints.
The worst case for this problem is simply that all of the unreliable edges fail and the problem
reduces to the usual shortest path problem. An alternative analysis of the problem can be made with
the help of competitive analysis, for this method of analysis. The offline algorithm known in advance
which edges will fail and the goal is to minimize the ratio between the online and offline algorithm
performance. This problem is PSPACE-complete.
Randomized Algorithm
In order to use probabilistic analysis, we need to know something about the distribution on the
inputs. In many cases, we know very little about the input distribution. Even if we do know
something about the distribution, we may not be able to model this knowledge computationally. Yet
we often can use probability and randomness as a tool for algorithms.
Consider the hiring problem. In the hiring problem, it may seem as if the candidates are being
presented to use in a random order, but we have no way of knowing whether or not they really are.
Thus, in order to develop a randomized algorithm for the hiring problem, we must have greater
control over the order in which we interview the candidates. We will, therefore, change the model
slightly. We will say that the employment agency has n candidates, and they send us a list of the
candidates in advance. On each day, we choose, randomly, which candidate to interviews. Although
we know nothing about the candidates (beside their names), we have made a significant change.
Instead of relying on a guess that the candidates will come to us in a random order, we have instead
of relying on a guess that the candidates enforced a random order. More generally, we call an
algorithm randomized if its behavior is determined not only by its input but also by values produced
by a random-number generator. We shall assume that we have at our disposal a random-number
generator RANDOM.
A call to RANDOM (a, b) returns an integer between a and b, inclusive, with each such integer being
equally likely.
For example, RANDOM (0, 1) produces 0 with probability 1/2, and it produces 1 with probability
1/2. A call to RANDOM (3, 7) retunes either3, 4, 5, 6 or 7, each with probability 1/5. Each integer
returned by RANDOM is independent of the integers returned on previous calls. You may imagine
RANDOM as rolling a (b a )-sided die to obtain its output.
Asymptotic Notation
Asymptotic analysis is based on two simplifying assumptions which hold in most (but not all
cases). But it is important to understand these assumptions and limitations of asymptotic
analysis.
Large input sizes: We are most interested in how the running time grows for large values of n
Ignore constant factors: The actual running time of the program depends on various constant
factors in the implantations (coding tricks, optimizations in compilations speed of the
underlying hardware etc.) Therefore we will ignore constant factors.
The asymptotic notations are used to represent the relative growth rate between functions.
Big Oh (‘O’)
Represent upper bound on the running time and the memory being consumed by the algorithms.
O (n) essentially conveys that the growth rate of running time/memory consumption rate will not
be more than “n” for all inputs of size n for a given algorithm. However, it may be less than this.
More formally Big-Oh is defined as follows:
The function f(n) = Og(n) if and only if f(n) c. g(n) for all n, n n where c, n are positive
constants.
Thus, if f(n) = O g(n) statement is said to be true then the growth rate of function g(n) is surely
higher than/equal to f(n).
We use O-notation to denote an upper bound that is not asymptotically tight
O (g (n)) = {f(n) : for any positive constant c > 0
There exists constant n0 > 0 0 f(n) cg(n) such that for all n n0+
Big-Oh Properties
1. If f (n) is O (g (n)) then a. f (n) is also O (g (n))
2. If f (n) is O (g (n)) and h (n) is O(p(n)) then f (n) + h(n) =O (max (g (n), P (n)))
3. If f (n) is O (g (n)) and h (n) is O (p (n)) then f (n) h (n) is O (g (n). p (n))
4. If f (n) is O (g (n)) and g (n) is O (h (n)) then f (n) is also O (h (n))
5. logn is O (logn) k
6. If f(n) is any polynomial of degree m, F(n) = a . n a n a n a , then f(n) is
O(n )
Θ Notation
C g(n)
f (n)
C g(n)
n
n0
f(n) = (g(n))
For a given function g(n), we denote by Θ(g(n)), the set of functions:
Θ (g(n)) = {f(n): there exists positive constants c1, c2
And n0 such that 0 c g(n) f(n) c (g(n))
For all n n0
Used when asymptotic lower bound is needed
Ω (g (n)) ={f(n): there exists positive constants c and
n such that 0 cg (n) f(n)
Theorem
For any two functions f (n) and g (n) we have f (n) = Θ (g(n)) if and only
if f(n) = O(g(n)) and f (n) = Ω (g(n))
Remarks
If f (x) = (g(x)) then g( x) is also (f(x))
If f (x) = (g(x)) we can say that f (x) is O (g(x)) and f (x) is Ω (g(x)) and also g(x) is
O (f(x)) and g(x) is Ω (f(x))
Small Oh (o)
The function f (n) = o (g(n)) iff
im f(n)
=o
n g(n)
Big Omega ( )
Big Omega represents lower bound on the running time and the memory being consumed by the
algorithms. Ω (n) essentially conveys that the growth rate of running time/memory consumption
rate will not be less than “n” for all inputs of size n for a given algorithm. However, it may be greater
than this.
More formally Big-Omega is defined as follows:
If f(x) and g (x) are any two functions and f (x) is Ω(g (x)),
If f (x) c. g(x) for x k where c and k are any two positive constants.
Thus, if f(x) is Ω(g (x)), statement is said to be true then the growth rate of function g(x) is surely
lower than/equal to f(x).
2. Reflexivity
f(n) = (f(n)),
f(n) = O (f(n)),
f(n) = Ω (f(n)).
3. Symmetry
f(n) = (g(n))if and only if g(n) = (f(n)).
4. Transpose Symmetry
f(n) = O (g(n)) if and only if g(n) = Ω(f(n)),
f(n) = O (g(n)) if and only if g(n) = ω(f(n)).
because these properties hold for asymptotic notations, one can draw an analogy between the
asymptotic comparison of two functions f and g the comparison of two real numbers a and b.
f(n) = O (g(n)) a b,
f(n) = Ω (g(n)) a b,
f(n) = (g(n)) a = b,
f(n) = o (g(n)) a b,
f(n) = w(g(n)) a b
We say that f (n) is asymptotically smaller than g (n) if f (n) = o (g(n)), and f (n) is
asymptotically larger than f (n) if f (n) = ω (g (n)).
One property of real numbers, however, does not carry over to asymptotic notation:
5. Trichotomy: For any two real numbers a and b, exactly one of the following must hold:
a < b, a = b, or a>b.
Some Examples
Example: f(n) = n
n n for all n
n = O (n)Here c = , n =
Example: f(n) = n n
n n n for n
n n = O (n )Here c = , n =
Example: f(n) = . n
. n
. n = O( ) for n
Example: n n O(n)
ecause here doesn’t exist any positive n and c so that Big-Oh equation gets satisfied.
Remarks - For the function 4n+3, 4n+3 is O(n)
4n+3 is also O(n ) and O(n )
Even though 4n+3 is O(n ) and O(n ) but the best answer for , 4n+3 is O(n) only, as
O(n) shows most tighter upper bound than the other in the question
If an algorithm has the time complexity O(1), then the time complexity is said to be
constant, that means running time is independent of input size.
Example: f(n) = n
n n for n
n n for n
n is Ω (n) Here c = 2, k = 1
We can also say that n n for then c = 1, k = 1
Remarks:
If f(n) is O (g (n)), then g (n) is Ω(f (n)).
Recurrence
A recurrence is an equation or inequality that describes a function in terms of its value on smaller
inputs.
Master Theorem
T(n) = aT ( ) (n log n)
Where a ,b , k > 0 and p is a real number
1. If a>b , them T(n) = (n
k )
2. If a = b k
12
Abraham Lincoln….
CHAPTER
Learning Objectives
After reading this chapter, you will know:
1. The General Method
2. Finding MaxMin
3. Binary Search
4. Sorting
5. Bubble sort
6. Merge Sort
7. Quick Sort
8. Insertion Sort
9. Selection Sort
10. Heap Sort
11. Straseen’s Matrix Multiplications
Finding MaxMin
The problem of finding Max Min is to find the maximum and minimum items in a set of ‘n’ elements.
The following Algorithm is a straightforward algorithm to accomplish the task.
Consider the problem of determining whether a given element x is present in the list. In case x is
present. We are to determine a value j such that a x if x is not in the list then j is to be set to zero.
Example: Consider the following elements in their ascending Order (i.e., the pre-requisite for
Binary Search)
The working of Binary Search for x: 101, , and 82 for two successful searches and one
unsuccessful search is as follows.
9 9 9 2 1 Not found
X = 82 Low High Mid
1 9 5
6 9 7
8 9 8
found
Sorting
Sorting algorithms used in computer science are often classified by:
Computational complexity (worst, average and best behaviour) of element comparisions in
terms of the size of the list. For typical sorting algorithms good behaviour is O(nlogn) and bad
behaviour is O(n2).
Memory usage (and use of other computer resources): In particular, some sorting algorithms
are "in place". This means that they need only O(1) memory beyond the items being sorted and
they don't need to create auxiliary locations for data to be temporarily stored, as in other
sorting algorithms.
Recursion: Some algorithms are either recursive or non-recursive, while others may be both
(e.g., Merge sort).
Stability: Stable sorting algorithms maintain the relative order of records with equal keys
(i.e., values).
Comparison: Whether or not they are a comparison sort. A comparision sort examines the data
only by comparing two elements with a comparison operator.
Adaptability: Whether or not the presortedness of the input affects the running time.
Algorithms that take this into account are known to be adaptive.
Bubble Sort
Bubble sort is a simple sorting algorithm. It works by repeatedly stepping through the list to be
sorted, comparing each pair of adjacent items and swapping them if they are in the wrong order.
First pass bubbles out the largest element and places it in the last position and second pass places
the second largest in the second last position and so on. Thus, in the last pass smallest item is placed
in the first position. Because, it only uses comparisions to operate on elements, it is a comparision
sort.
E.g.: Let us take the array of numbers "5 1 4 2 9", and sort the array from lowest number to greatest
number using bubble sort algorithm. In each step, elements written in bold are being compared.
Performance
The running time can be bound as the total number of comparisons being made in the entire
execution of this algorithm. Thus, in the worst case (input is descending order) numbers of
comparisons in the respective passes are as follows:
1st n–1
2nd n – 2
…
(n – 1)th pass 1
Therefore, total comparisons = n(n 1)/2;
Which implies O (n2) time complexity
Bubble sort has worst-case and average complexity both as О (n²), where n is the number of items
being sorted. Performance of bubble sort over an already-sorted list (best-case) is O(n). Remember
that using a flag it can easily be determined that if no swaps were performed in the current pass that
would mean the list is sorted and bubble sort can come out, rather than going through all the
remaining passes.
Remarks
It is comparison based sorting method.
It is in place sorting method.
Worst case space complexity is of O (1)
It is adaptive sorting method as presortedness affects the running time.
A stable sorting method.
Merge Sort
Merge Sort is a divide-and-conquer strategy that sorts the elements in non-decreasing order.
Given a set of n-elements also called keys a [ ] ….. a [n] the concept is to split them into two
sets a[ ] …. a[[n ]] and a [[n ]+ ] …a[n]. Each set is individually sorted and the resulting
sorted sequences are merged to produce a single sorted sequence of n elements.
Thus, Marge Sort splits the given set of elements into two equal-size sets and the combining
operation is the merging of two sorted sets into one.
Merge Sort (Algorithm) describes this process using recursion an a function Merge (Algorithm)
which merge two sorted sets.
Merge Sort is an O(n log n) comparison-based divide and conquer sorting algorithm.
Conceptually, a merge sort works as follows:
1. If the list is of length 0 or 1, then it is already sorted. Otherwise:
2. Divide the unsorted list into two sub lists of about half the size.
3. Sort each sub list recursively by re-applying merge sort algorithm.
4. Merge the two sub lists back into one sorted list.
Example:
39 27 43 3 9 82 10
39 27 43 3 9 82 10
39 27 43 3 9 82 10
39 27 43 3 9 82 10
27 39 3 43 9 82 10
3 27 39 43 9 10 82
3 9 10 27 39 43 82
Elements a [6] and a [7] are merged. Then a [8] is merged with a [6: 7]:
(179, 285, 310, 351, 652,! 254, 423, 450, 861, 520,)
Next a [9] and a [10] are emerged and then a [6: 8] and a [9: 10]
(179, 285, 310, 351, 652! 254, 423, 450, 520, 861)
Now there are two sorted sub-arrays and the final merge produce the fully sorted
result (179, 254, 310, 285, 351, 423, 450, 520, 652, 861)
Performance
In sorting n objects, merge sort has an average and worst-case performance of O(n log n). If the
running time of merge sort for a list of length n is T(n), then the recurrence T(n) = 2T(n/2) + O(n)
follows from the definition of the algorithm (apply the algorithm to two lists of half the size of the
original list, and add the n units taken to merge the resulting two sorted lists).
Thus, after simplifying the recurrence relation T(n) = O(nlogn).
Space Complexity
The stack space is necessitated by the use of recursion of Merge Sort.
Since merge sort splits each set into two approximately equal-sized subsets, the maximum depth
of the stack is proportional to log n.
The need for stack space seems indicated by the top-down manner in which this algorithm was
devised.
The need for stack space can be eliminated if we build an algorithm that works bottom-up
Limitations: One of the drawbacks of merge sort is its use of the auxiliary array (additional storage).
The Solution: An alternative to associate a new field of information with each key. (The elements in a
[ ] are called keys). This field is used to link the keys and any associated information together in a
sorted list (keys and related information are called records). Then the merging of the sorted list
proceeds by changing only a link values, and no records need be moved at all. A field that contains
only a link will generally be smaller than an entire record, so less space will be used.
Along with the original array a [], we define an auxiliary array ling (1: n) that contains integers in
the range [0, n]. These integers are interpreted as pointers to elements of a []. A list is a sequence of
pointers ending with a zero. Below is one set of values for ling that contains two lists: Q and R. The
integer Q = 2 denotes the start of one list and R = 5 the start of the other.
Link: [1] [2] [3] [4] [5] [6] [7] [8]
6 4 7 1 3 0 8 0
The two lists are Q = (2, 4, 1, 6) abd R = (5, 3, 7, 8). Interpreting these lists as describing sorted
subsets of a [1: 8], we conclude that a [2] a [4] a [1] a [5] a [3] a [7] a [8].
An Alternative to Merge Sort: (Insertion Sort)
Insertion sort works exceedingly fast on arrays of less than, say 16 elements, though for large n its
computing time is O (n ). Its basic idea for sorting the items in a [1: n] is as following:
For j = 2 to n do
{
Place a [j] in its correct position in the sorted set a [1: j 1];
The Algorithm is initially invoked by placing the keys of the records to be sorted in a [1: n] and
setting link [1: n] to zero.
A pointer to a list of indices that give the elements. Of a [] in sorted order is returned. Insertion
sort is used whenever the number of items to be sorted is less than 16.
The version of insertion sort need to be altered so that it sorts a [low: high] into a linked list.
Quick Sort
Quick Sort is a Divide-and-Conquer approach which is an efficient sorting method, different from
merge sort.
In quicksort, the division into two sub arrays is made so that the sorted sub arrays do not need to be
merged later. This is accomplished by rearranging the elements in a [1: n] such that a [i] a[j] for
all i between 1 and m and all j between m + 1 and n for some m, 1 m n.
Thus, the element in a [1: m] and a [m+1: n] can be independently sorted. No merge is needed.
The rearrangement of the elements is accomplished by picking some element of a [], say t = a [s],
and then reordering the other elements so that all elements appearing before t in a [1: n] are less
than or equal to and all elements appearing after t are greater than or equal to t. this rearranging is
referred to as partitioning.
Function Partition of Algorithm, partitions elements of a [m: p 1]. It is assumed that a [p] a [m]
and that a [m] is the partitioning element. If m = 1 and p = n, then a [n+1] must be define and
must be greater than or equal to all elements in a [1: n]. The assumption that a [m] is the partition
element is merely for convenience; other choices for the partitioning element than the first item in
the set are better in practice. The function interchange (a, I, j) exchanges a [i] with a [j].
Example: Consider the following set of element. The function is initially invoked as partitions (a, 1,
10). The element a [1] = 65 is the partitions elements and it eventually (in the sixth row)
determined to the fifth smallest element of the set. Notice that the remaining elements
are unsorted but partitioned about a [5] = 65.
(1) (2) (3) (4) (5) (6) (7) (8) (9) (10) i p
70 75 80 85 60 55 50 45 + 2 9
45 75 80 85 60 55 75 70 + 3 8
45 50 80 85 60 55 75 70 + 4 7
45 50 55 85 60 80 75 70 + 5 6
45 50 55 60 85 80 75 70 + 6 5
45 50 55 65 85 80 75 70 +
We can device a divide-and-conquer method for completely sorting n elements. Function
partition, creates two sets S and S . All elements is S are less than or equal to the
elements in S Hence S and S can be sorted independently. Each set is sorted by reusing
the function partition. The following Algorithm describes the complete process.
Algorithm: Partition
1. Algorithm Partition (a, m, p)
2. Within a [m] a [m+ ] ….a [p 1] the elements are Rearranged in such a manner that if
initially t = a [m], Then after completion a [q] = t for some q between m and p 1, a [k] t for m
k < q, and a [k] t for q < k < p, q is returned. Set a [p] = .
3. {
4. v = a [m]; i = m; j = p;
5. Repeat
6. {
7. Repeat
8. i = i + 1;
9. Until (a [i] v);
10. Repeat
11. J = j ;
Quick sort sorts by employing a divide and conquer strategy to divide a list into two sub-lists.
The simple steps to remember are:
1. Pick an element, called a pivot, from the list.
2. Reorder the list so that all elements which are less than the pivot come before the pivot and so
that all elements greater than the pivot come after it (equal values can go either way). After this
partitioning, the pivot is in its final position. This is called the partition operation.
3. Recursively sort the sub-list of lesser elements and the sub-list of greater elements.
The base case of the recursion are lists of size zero or one, which are always sorted.
Performance
If the running time of quick sort for a list of length n is T(n), then the recurrence
T(n) = T(size of s1) + T(size of s2) + n follows from the definition of the algorithm (apply the
algorithm to two sets which are results of portioning, and add the n units taken by the portioning of
the given list).
Insertion Sort
Insertion Sort: Is a simple comparison sorting algorithm. Every iteration of insertion sort removes
an element from the input data, inserting it into the correct position in the already-sorted list, until
no input element remains in wrong position. The choice of which element to remove from the input
is arbitrary, and can be made using almost any algorithm.
Example
Pass 1
54321 45321
Pass 2
45321 43521 34521
Pass 3
34521 34251 32451 23451
Pass 4
23451 23415 23145 21345 12345
Performance
The worst case input is an array sorted in reverse order. In this case every iteration of the inner loop
will scan and shift the entire sorted subsection of the array before inserting the next element. For
this case insertion sort has a quadratic running time (i.e., O (n2)). The running time can be bound as
the total number of comparisons being made in the entire execution of this algorithm. Thus, the
worst case comparisions in the respective passes are as follows:
1st 1
2nd 2
3rd 3
(n – 1)th pass n 1
Therefore, total comparisons = n (n 1)/2;
Which implies O (n2) time complexity.
The best case input is an array that is already sorted. In this case insertion sort has a linear running
time (i.e., O (n)). During each iteration, the first element of the input is only compared with the
right-most element of the sorted subsection of the array.
Insertion sort typically requires more writes because the inner loop can require shifting large
sections of the sorted portion of the array. In general, insertion sort will write to the array O (n2)
times, whereas selection sort will write only O (n) times. For this reason selection sort may be
preferable in cases where writing to memory is significantly more expensive than reading.
Remarks: It is much less efficient on large lists than more advanced algorithms such as quicksort,
heap sort, or merge sort. However, insertion sort provides several advantages
Simple implementation
efficient for (quite) small data sets
Adaptive, i.e. efficient for data sets that are already substantially sorted the time complexity is
O (n)
Stable, i.e. does not change the relative order of elements with equal keys
In-place, i.e. only requires a constant amount O (1) of additional memory space
Most humans when ordering a deck of cards, for example—use a method that is similar to
insertion sort.
Selection Sort
Selection sort is also a simple comparison sorting algorithm. The algorithm works as follows:
1. Find the minimum value in the list
2. Swap it with the value in the first position
3. Repeat the steps above for the remainder of the list (starting at the second position and
advancing each time)
Effectively, the list is divided into two parts: the sublist of items already sorted, which is built
up from left to right and is found at the beginning, and the sublist of items remaining to be
sorted, occupying the remainder of the array.
Here is an example of this sort algorithm sorting five elements
66 25 12 22 11, 11 25 12 22 66, 11 12 25 22 66, 11 12 22 25 66, 11 12 22 25 66
Performance
All the inputs are worst case inputs for selection sort as each current element has to be compared
with the rest of unsorted array. The running time can be bound as the total number of comparisons
being made in the entire execution of this algorithm. Thus, the worst case comparisions in the
respective passes are as follows:
1st n–1
2 nd n–2
…
(n – 1)th pass 1
Therefore total comparisions = n(n 1) / 2;
Which implies O (n2) time complexity.
Remarks:
It is much less efficient on large lists than more advanced algorithms such as quicksort,
heapsort, or merge sort.
Simple implementation.
Efficient for (quite) small data sets.
Not adaptive.
Its stability depends on the implementation of choosing minimum.
In-place, i.e. only requires a constant amount O (1) of additional memory space.
Insertion sort is very similar to selection sort as after the kth iteration; the first k elements in the
array are in sorted order. Insertion sort's advantage is that it only scans as many elements as it
needs in order to place the k + 1st element, while selection sort must scan all remaining
elements to find the k + 1st element.
Selection sort always performs O (n) swaps.
Heap Sort
Heap Sort is a comparison-based sorting algorithm which is much more efficient version of selection
sort. It also works by determining the largest (or smallest) element of the list, placing that at the end
(or beginning) of the list, then continuing with the rest of the list, but accomplishes this task
efficiently by using a data structure called a heap. Once the data list has been made into a heap, the
root node is guaranteed to be the largest (or smallest) element. When it is removed (using
deleteMin/deleteMax) and placed at the end of the list, the heap is rearranged so that the largest
element of remaining moves to the root. Using the heap, finding the next largest element takes O
(log n) time, instead of O (n) for a linear scan as in simple selection sort. This allows Heapsort to run
in O (n log n) time.
Remarks:
Its not adaptive.
It is in-place sorting method as it utilized the same input array for placing the sorted sub array.
Not a stable sorting method as during deleteMin/deleteMax the order is not preserved for the
same key values. Consider an input that is having all the same key values. The deleteMin will
pick up the last heap element to place it in the root location. Thereby, the order is changed
because in the sorted output later values appear before.
For all i and j between 1 and n. To compute C (i, j) using this formula, we need n multiplication. As
the matrix C has n elements, the time for the resulting matrix multiplication algorithm, which we
refer to as the conventional method is n .
The Solution: The divide-and-conquer strategy suggests another way to computer the product of
two n x n matrices. For simplicity we assume that n is a power of 2, that is there exists a non-
negative integer k such that n = in case is not a power of two then enough rows and column of
zeros can be added to both A and B so that the resulting dimensions are a power of two.
Imagine that A and B are each partitioned into four square sub matrices, each sub matrix having
dimensions n/2 × n/2. Then the product AB can be computed by using the above formula for the
product of 2 × 2 matrices: if AB is
( ) ( ) ( )
Then
+
+
+
+
These elements are typically floating point numbers. For n >2, the elements of C can be computed
using matrix multiplication and addition operations applied to matrices of size n/2 × n/2. Since n is
a power of 2, these matrix products can be recursively computed by the same algorithm we are
using for the n × n case. This algorithm will continue applying itself to smaller-sized sub matrices
directly.
Since two n/2 × n/2 matrices can be added in time cn for some constant c, the overall computing
time T (n) of the resulting divide-and-conquer algorithm is given by the recurrence
n b n
n + cn n >2
Where b and c are constants.
13
….Confucius
CHAPTER
Greedy Techniques
Learning Objectives
After reading this chapter, you will know:
1. General Method
2. Minimum Spanning Tree
3. Prim’s Algorithm
4. Kruskal’s Algorithm
5. Single Source Shortest Path Algorithm
General Method
The greedy method is a design technique applied to those problems having n inputs and
requires us to obtain a subset that satisfies some constraints.
Any subset that satisfies these constraints is called a feasible solution.
We are required to find a feasible solution that either maximizes or minimizes a given objective
function.
A feasible solution that does this is called an optimal solution.
The greedy method suggests that one can design an algorithm which works in stages,
Considering one input at a time.
Consider the inputs in an order determined by some selection procedure. If the inclusion of the
next input into the partially constructed optimal solution will result in an infeasible solution,
then this input is not added to the partial solution.
The selection procedure is based on some optimization measure. This measure may or may not
be the objective function.
The function SELECT selects an input from A, and assigns its value to x.
FEASIBLE is a Boolean-valued function which determines if x can be included into the
solution vector.
UNION combines x with solutions and updates the objective function.
Example: Consider three tapes with their lengths as (l l l ) = (5, 10, 3). There are n! = 6 possible
ordering. These orderings and their respective D values are:
Ordering 1 D(I)
A, 2, 3 5+5+10+5+10+3=38
1, 3, 2 5+5+3+5+3+10=31
2, 1, 3 10+10+5+10+5+3=43
2, 3, 1 10+10+3+10+3+5=41
3, 1, 2 3+3+5+3+5+10=29
3, 2, 1 3+3+10+3+10+5=34
The Optimal Ordering is 3, 1, 2
A greedy approach of building the required permutation would choose the next
program based upon some optimization measure.
One possible measure would be the D value of the permutation constructed so far.
The next program to be stored on the tape would be one which minimized the
increase in D.
If we have already constructed the permutation i i … . . i i i. This increases
the D value by .
The greedy method suggests us to store the program in non-decreasing order of their
length. This ordering can be carried out in O (n log n) time using an efficient sorting
algorithm.
Knapsack Problem
Given ‘n’ objects and a knapsack (a bag). Object have a weight w , and the knapsack has a
capacity m.
If a fraction a , 0, < i, < 1, of object I is placed into the knapsack, then a profit of pi, is earned.
The objective is to obtain a filling of the knapsack that maximizes the total profit earned.
Since the knapsack capacity is m, we require the total weight of all chosen objects to be at most m.
The problem of knapsack can be stated as follows:
info@[Link] ©Copyright reserved. Web:[Link] 30
Greedy Techniques
ax P X ( )
Subject to Wi Xi m (2)
And Xi, = 0 or 1, 1 i n ( )
The problem of knapsack are positive numbers.
A feasible solution (or filling) is any set (xi ….Xn) satisfying (2) and ( ) above.
An optimal solution is a feasible solution for which (1) is maximized.
The “Tree Vertex Splitting Problem (TVSP) is to determine an optimal placement of boosters”.
The TVSP can be specified as follows:
Let T = {V, E, w} be a weighted directed tree, where V is the vertex set, E is the edge set, and w is
the weight function for the edges.
W (i, j) is the weight of the edge <i, j> in E. the weight w (i, j) is undefined for any {i, j} not in E.
A source vertex is a vertex with in-degree zero, and sink vertex is a vertex with out-degree zero.
For any path P in the tree, its delay. D (P), is defined to be the sum of the weight on that path.
The delay of the tree T, d (T), is the maximum of all the path delays.
For the TVSP, the quantity that is optimized (minimized) is the number of nodes in X.
A greedy approach to solving this problem is to compute for each node u ϵ V, the maximum delay
d (u) from u to any other node in its sub-tree.
If u has parent v such that d (u) + w (v, u) > , then the node u gets split and d (u) is set to zero.
Computation proceeds from the leaves toward the root.
The order in which TVS visits the nodes of the tree is called the post order.
Algorithm TVS Takes ( ) where n is the number of nodes in the tree. This can be seen as
follows;
When TVS is called on any node T, only a constant number of operations are performed
(excluding the time taken for the recursive calls). Also, TVS is called only once on each node T in
the tree.
To complete a job, one has to process the job on a machine for one unit of time. Only one machine
is available for processing jobs.
A feasible solution for this problem is subset j of job such that each job in this subset can be
completed by its deadlines. The value of a feasible solution j is the sum of the profits the jobs in j.
An optimal solution is a feasible solution with maximum value. Since the problem involves the
identification of a subset, it fits the subset paradigm.
Example: Let n = 4, Penalties (p1, p2, p3, p4) = (100, 10, 15, 27) and Deadlines (d1, d2, d3, d4) =
(2, 1, 2, 1).
The feasible solution and their values are:
Feasible Processing Solution Sequence Value (Profit)
(1, 2) 2, 1 110
(1, 3) 1, 3 or 3, 1 115
(1, 4) 4, 1 127
(2, 3) 2, 3 25
(3, 4) 4,3 42
(1) 1 100
(2) 2 10
(3) 3 15
(4) 4 27
Solution 3 is Optimal: In this solution only jobs 1 and 4 are processed and the value is 127
These jobs must be processed in the order job 4 followed by job 1. Thus the processing of
job 4 begins at time zero and that of job 1 is completed at time 2.
A Greedy Algorithm for job sequencing with Deadlines and Profits Algorithm JS (d, j, n)
1. {
2. b[0] = j [0] = 0;
3. j [1] = 1;
4. k: = 1;
5. For i = 2 to n do
6. {
7. r = k;
8. While (d [j[r]]> d [i]) and (d[j[r]] r+) do r = r ;
9. if (d[j[r]] d[z]) and (d[j[r]]>r)) then
10. {
Spanning Tree: A minimum cost spanning tree is a spanning tree such that the sum of all the weights
of edges in spanning tree is minimum.
Spanning tree is a connected sub-graph without any circuit, basically determine from a graph.
Prim’s Algorithm
This algorithm computes the minimum spanning tree by including appropriate vertex and thus one
edge into existing partially constructed tree in successive stages.
At any point in the algorithm, we can observe that we have a set of vertices that have already been
included in the tree; the rest of the vertices have not. The algorithm then finds, at each stage, a new
vertex to add to the tree by choosing the edge (u, v) such that the cost of this edge is the smallest
among all edges where u is in the tree and v is not.
The Prim’s algorithm is essentially identical to Dijkstra’s only except the update rule.
The new update rule is as follows:
dw = min(dw, cw,v). Thus the RT analysis of Dijkstra’s algorithm will remain applicable here too.
Algorithm work as given below,
Algorithm:
The Prims algorithm is given below.
A *V ,v- V * *r+ Q++
MST – PRIM (G w r)
G Graph
[ w Weight ]
r Source Vertex
1. for each u ϵ V,G-
2. do key ,u-
3. ,u-
4. key,r-
info@[Link] ©Copyright reserved. Web:[Link] 34
Greedy Techniques
Example: This example will walk through all stages of Prim’s algorithm for the following graph.
V V
2
1 3 10
4
2 2
V V V
4
5 8
V 1 6
V
V C=0 V C=2
V C=4 V C=1 V
V V
Left V1 is Declared Known;
V C=0 V C=2
V C=2 V V C=7
V C=8 V C=4
Right V4 is Declared Known
V C=0 C=2
V
V V C=1 V C=7
C=2
V C=5 V C=4
V2 and then V3 are Declared Known
C=0
V V C=2
V V C=1 V C=6
C=2
V C=1 V C=4
Left V7 Declared Known
C=0
V V C=2
V V C=1 V C=6
C=2
V V C=4
C=1
Right V6 is Declared Known
C=0 2
V V C=2
2 C=1
V V V C=6
C=2
4
6
V V C=4
1
C=1
V5 is Declared Known
Spanning tree weight 2 2
Kruskal’s Algorithm
Initially from the graph G, consider a forest of all the vertices without any edge.
1. Sort all the edges in ascending order according to their costs.
2. Include the edge with minimum cost into the forest if it does not form a cycle in the partially
constructed tree. Repeat step (2) until no edge can be added to the tree.
Algorithm work as given below,
Algorithm:
The Kruskal’s algorithm is given below.
MST – KRUSKAL (G, w)
1. A
2. For each vertex vϵV do MAKE-SET
3. Sort the edges of E into non-decreasing order according their weight W.
4. For each edge (u v) ϵ taken in non-decreasing order by weight.
5. Do if FIND-SET(u) FIND-SET (v)
6. Then A A *(u v)+
7. UNION (u v)
8. Return A
Above Kruskal algorithm takes two input the graph G and weight of each edge W.
Now we shows how this algorithm actually works using the following example.
E.g.: This example will walk through all stages of Kruskal’s algorithm for the following graph.
2
V V
4 3 10
1
2 7
V V V
8 4
5 6
V V
1
Let sort the edges based on their cost.
(V6, V7, 1) (V1, V4, 1) (V1, V2, 2) (V3, V4, 2) (V2, V4, 3) (V1, V3, 4) (V4, V7, 4) (V3, V6, 5) (V5, V7, 6)
(V4, V5, 7) (V4, V6, 8) (V2, V5, 10).
V V
V V V
V V
V V
V V V
V V
1
Right V6V7 is Added
V V
1
V V V
V V
1
Left V1V4 is Added
2
V V
1
V V V
V V
1
Right V1V2 is Added
2
V V
2
V V V
V V
1
Left V3V4 is Added
2
V V
2
V V V
4
V V
1
Right V4V7 is Added
2
V V
2
V V V
4
6
V V
1
V5 and V7 is Added
Thus, the cost of minimum spanning tree is 16. The following three edges were rejected by the
algorithm though they were selected, (V2, V4, 3) (V1, V3, 4) (V3, V6, 5). That means, it is possible
for a minimum spanning tree to have greater cost edges included while not including the lower cost
edges(because these cause cycles).
Remarks: If the weight of all the edges of a graph G is unique, then only one minimum spanning tree
exists for that graph.
If the weights of all the edges of a graph G are not unique, then the graph might have only one
minimum spanning tree or more than one also which are structurally different.
dist[w] = dist[v] + 1;
path [w] = v;
enqueueQ (w);
}
}
}
Example: The following diagram illustrates state of graph just after declaring V1 as known vertex.
There is an update in cv of V2 and V4 vertices also.
0 2
V INF
V
1 3 10
4
2 2
INF V V V INF
INF
4
5 8
6
V 1 V
INF
INF
0 2
*V C=2
V
1 3 10
4 C=1
2 2
INF V V V INF
INF
4
5 8
6
V 1 V
INF
INF
V1 is Chosen Because it was the Unknown Vertex with Lowest Cost
0 2
*V V C=2
1 3 10
4
C =1
C=3 V 2 *V 2
V C=3
4
5 8 6
V 1 V
C=9 C=5
Left V4 is Made Known
0 2
V C=2
V
1 3 10
4
C=1
2 2
C=3 V V V C=3
INF
4
5 8
6
V 1 V
C=9 C=5
Right V2 is Made Known
0 2
V V C=2
1 3 10
4
C=1
C=3 V 2 2
V V C=3
4
5 8
6
V 1 V
C=9 C=5
Left V5 is Made Known
0 2
V V C=2
1 3 10
4
C=1
2 2
C=3 V V V C=3
4
5 8
6
V 1 V
C=8 C=5
Right V3 is Made Known
0 2
V V C=2
1 3 10
4
C=1
C=3 V 2 2
V V C=3
4
5 8
6
V 1 V
C=6 C=5
Left V7 is Made Known
0 2
V C=2
V
1 3 10
4
C=1
2 2
C=3 V V V C=3
4
5 8
6
V 1 V
C=6 C=5
Right V6 is Made Know
Each stage of the algorithm considers exactly one vertex whose cost is lowest to be marked as
known. That means algorithm will consist of |V| stages. Now we need to analyze RT of each stage. In
general MinHeap is used for implementing this algorithm efficiently. Minheap is implemented for
vertex cost, and then surely root node will always be the next vertex to be picked up by algorithm.
DeleteMin will return the vertex having the lowest cost. And decrease key can be used for updating
the Cw. Thus, if graph is represented using adjacency list then;
The RT of for loop for processing vertex v1 is = log|V| + |e1|log|V|
The RT of for loop for processing vertex v2 is = log|V| + |e2|log|V|
Therefore, total RT is = |V|log|V| + |E|log|V| = (|E| + |V|)log|V|.
However, if graph is represented using adjacency matrix, then
RT is = |V|log|V| + |V|2log|V| = O (|V|2log|V|).
r r
(A) g ( ) g
2 2
s s
2 2
y z y z
r
( ) g r
(D) 2 g
2
2
s
s
2 2
2
y z 2
y z
r
( ) 2 g
s
2
2
2
y z
Example: onsider the following files (x ….x ) with size (2 ) the greedy method
generate the following merge pattern:
Merge x4 and x3 to get z1 (|z1|=15), merge z1 and x1 to get z2 (|z2|=35), merge x2 and
x5 to get z3 (|z3|=60), and merge z2 and z3 to get the answer z4.
The total number of record moves is 205.
This type of merge pattern is called as a two-way merge pattern (each merge step
involves the merging of two files). The two-way merge pattern can be represented by
binary merge tress.
The leaf nodes are drawn as squares and represent the given files. These nodes are
called external nodes. The remaining nodes are drawn as circles and are called
internal nodes.
Each internal node has exactly two children, and it represents the file obtained by
merging the files represented by its two children. The number in each node is the
length (i.e., the number of records) of the file represented by that node.
The greedy method to generate merge trees also works for the case of k-ary merging.
In this case the corresponding merge tree is a k-ary tree. Since all internal nodes must have
degree k, for certain values of n there is no corresponding k-ary merge tree.
For example, when k = 3, there is no k-ary merge tree with n = 2 external nodes. Hence, it is
necessary to introduce a certain number of dummy external nodes. Each dummy node is
assigned a qi, of zero. This dummy value does not affect the weighted external path length of the
resulting k-ary tree.
The greedy rule to generate optimal merge trees is: At each step choose k sub-trees with least
length for merging
Huffman Codes
An application of binary tree with minimal weighted external path length is to obtain an optimal
set of codes for messages ….. n .
Each code is a binary string that is used for transmission of the corresponding message. At the
receiving end the code is decoded using a decode tree. A decode tree is a binary tree in which
external node represent messages.
The Binary Merge Tree
T q q
q q
The binary bits in the code word for a message determine the branching needed at each level of
the decode tree to reach the correct external node.
If we interpret a zero as a left branch and a one as a right branch then the decode tree of the
above Diagram corresponds to codes 000, 001, 01, and 1 for messages M1, M2, M3, and M4
respectively. These codes Huffman codes.
The cost of decoding a code word is proportional to the number of bits in the code. This number
is equal to the distance of the corresponding to the external node from the root node.
Huffman Code
4
….Bill Gates
CHAPTER
Dynamic Programming
Learning Objectives
After reading this chapter, you will know:
1. Idea of Dynamic Programming
2. Traveling Salesperson Problem (TSP)
3. Matrix Chain Multiplication Algorithm
4. 0/1 Knapsack
5. Optimal Binary Search Trees (OBST)
6. Longest Increasing Subsequences
7. Knapsack with Repetition
8. Memoization
Introduction
In the preceding chapters we have seen some elegant design principles. Such as divide-and conquer,
graph exploration, and greedy choice. That yield definitive algorithms for a variety of important
computational tasks. The drawback of these tools is that they can only be used on very specific types
of problems. We now turn to the two sledgehammers of the algorithms craft, dynamic programming
and linear programming, techniques of very broad applicability that can be invoked when more
specialized methods fail. Predictably, this generality often comes with a cost in efficiency.
Dynamic programming is an algorithm design method that can be used when the solution to a
problem can be viewed as the result of a sequence of decisions
For any problem, it is not possible to make stepwise decisions (based only on local information)
in such a manner that the sequence of decisions made is optional
One way to solve problem for which it is not possible to make a sequence of stepwise decisions
leading to an optimal decision sequence is to try all possible decision sequences.
In dynamic programming an optimal sequence of decisions is obtained by making explicit
appeal to three principle of optimality.
Principle of optimally: The principle of optimality states that an optimal sequence of decision
has the property that whatever the initial state and decision are, the remaining decisions must
constitute an optimal decision sequence with regard to the sate resulting from the first decision.
The difference between the greedy method and dynamic programming is that in the greedy
method only one decision sequence is ever generated. In dynamic programming many decision
sequences may be generated.
for i = 1 to n
for j = 1 to n
dist (i, j, k) = min{dist (i, j, ) + dist(k, j, ), dist (i, j, )}
Case 2:
Thus computing the product as rather than A (BC) reduces the number of scalar
multiplications by ( ) - a significant improvement.
{
∑
r [ ] st ata u r.
The number of solutions are thus exponential in n, and the brute force method of exhaustive search
is therefore a poor strategy for determining the optimal parenthesization of a matrix chain.
Let be the minimum number of scalar multiplications needed to compute matrix the cost
of the cheapest way to compute ….. would be
We can define recursively as follows:
If i = j, then it contains just one matrix . So no scalar multiplication is necessary to compute the
product. Thus,
r ….. 3.
Now if i > j the is minimum if we place a parenthesis between and . So is
minimum if computing the sub-product …. a plus the cost of multiplying the matrices
together.
…. take number of scalar multiplications.
T ss va u s ar ….. – 1.
So the cost of parenthesizing the product … . becomes
If we use the recursive algorithm then it will take exponential time – no better than the brute force
method of checking each way of parenthesizing the product. So we use dynamic programming
approach.
We know has the dimension for … . . . . Let P[n]be an array of length n + 1 which
stores the dimension of the array, …. …. auxiliary table for storing m[i ,j] cost …. ..
auxiliary table that records which index of k achieved the optimal cost in computing .
m[I, j]
for t
do
if
then
Return m and
The algorithm first computes for … . when length of the sequence is 1. It then
computes for … (the minimum cost for chains of length 2) during first execution
of the loop (for t . The second time through it computes for i …. -2 (the
minimum cost for chains of length 3). And so forth. At each step, the cost computed depends
only on table entries and already computed.
Time Complexity
A simple inspection of the nested loop structure of MATRIX_CHAIN_ORDER yields a running time of
for the algorithm. There is nested for loop of depth 3. The loop indices a take on at
most n values.
0/1 KNAPSACK
Given: A set S of n items, associated with each item i having
wi = A positive weight
bi = A profit
Goal: Choose items with maximum total profit but with weight at most W.
If we are not allowed to take fractional amounts then this is the 0/1 knapsack problem
r ‘ ’ at s t t ss t t t a sa a ‘ ’ at s t t ts t .
In this case, we let T denote the set of items we take
Objective:
ax z ∑
Constraint:
∑w
bi- A s t v “ r t”
wi- A s t v “w t”
Goal: Choose items with maximum total benefit but with weight at most W
Given
Weight 4 kg 2 kg 2 kg 6 kg 2 kg
Profit $20 $3 $6 $25 $80
Solution: Item 5 ($80, 2 in)
Item 3 ($6, 2 in)
Item 1 ($20, 4 in)
Example: Consider another knapsack instance where n = 3(w1, w2, w3) = ( 2, 3, 4) and(P1, P2,
P3)=(1, 2, 5) and m = 6 for these data we have
} }
} 3 3 }
3 3 } }
3 }
Now the pair (3, 5) has to be eliminated from as a result of the purging rule
When generating the we can also purge all pairs (P, W) with W >m
Knapsack Algorithm
1. Algorithm DKP(p, w, n, m)
2. {
3. }
4. for i=1 to do
5. {
6. { | ϵ a }
7. r ur ( )
8. }
9. ast a r
10. (PY, WY) w w r st ar st
11. Any pair in su t at
12. t x
13. s x
14. Tra a r x ….x
15. }
Reliability Design
The problem of Reliability Design is to design a system that is composed of several devices
connected in series.
Let r, be the reliability of device Di (that is, r, is the probability that device I will function
properly).
The reliability of the entire system is r . Even if the individual devices are very reliable i.e., the
ri’s ar v ry s t t r a ty t syst ay t v ry .
For example, if n = 10 and r = 0.99, 1 < i < 10, then r, = 0.904.
The solution is to duplicate copies of the devices. Multiple copies of the same device type are
connected in parallel as shown below.
If stage i contains m, copies of device D,, then the probability that all m, have a malfunction is
r . Hence the reliability of stage I becomes 1 – r
Consider if r = 0.99 and m = 2 the stage reliability becomes 0.9999. In any practical situation, the
stage reliability is a little less than 1 r
The problem is to use device duplication to maximize reliability.
Let be the cost of each unit of device i and let c be the maximum allowable cost of the system
being designed. We wish to solve the following maximization
Problem:
Maximize
Where is the reliability and is the number of devices
r r
w t
t
w
(a) (b)
r
An optimal binary search tree with root a a as t a r as r t
Example
Consider the following set of identifier (do, if, while)
The Possible binary search trees can be formed as follows
w
a
w
The cost I unction for a Optimal Binary search Tree can be given as follows
} w
w r v w
a w r
Algorithm for OBST
1. Algorithm OBST(p, q, n)
2. {
3. For i = 0 to n – 1 do
4. {
5. w r
6. w
7. r
8.
9. }
10. w r
11. r t t a tr ss w t s
12. r t
13. {
14.
15. w[i, j]=w[i, ] +p[j]+q[j]
16. r
17. w
18. r
19. }
20. wr t w r
21. }
1 5
8 9
7 5 1 2 10
2 10 7 9
8
r
9 2 10 4 6
7 8
3. Array Representation of
t [1] [2] [3] [4] [5] [d] [7] [8] [9] [10]
P 3 3 1 1 1 5
Collapsing rule
If j is a node of the path from I to its root p [i] root [i], then set p [j] to root [i].
Find algorithm with collapsing rule
1. Algorithm collapsing find (i)
2. {
3. R: = i;
4. While (p [r]>0) do r = p [r];
5. While (i r) do
6. {
7. S = p [i] p [i] = r; i: = s;
8. {
9. Return r;
10. }
5 52 28 86 63 36 69 97 7
In this example, the arrows denote transitions between consecutive elements of the optimal
solution. More generally, to better understand the solution space, let's create a graph of all
permissible transitions: establish a node i for each element a , and add directed edges (i, j)
whenever it is possible for a and a to be consecutive elements in an increasing subsequence, that is,
whenever i < j and a < a .
Notice that (1) this graph G = (V, E) is a dag, since all edges (i, j) have i < j, and (2) there is a one-to-
one correspondence between increasing subsequences and paths in this dag. Therefore, our goal is
simply to find the longest path in the dag!
Here is the algorithm:
r …
L(j) = 1 + max {L(i): (i, j) ϵ E}
Return max L(j)
L(j) is the length of the longest path the longest increasing subsequence ending at j (plus 1, since
strictly speaking we need to count nodes on the path, not edges). By reasoning in the same way as
we did for shortest paths, we see that any path to node j must pass through one of its predecessors,
and therefore L(j) is 1 plus the maximum L(.) value of these predecessors. If there are no edges into
j, we take the maximum over the empty set, zero. And the final answer is the largest L(j), since any
ending position is allowed.
This is dynamic programming. In order to solve our original problem, we have defined a collection
of subproblems } with the following key property that allows them to be solved in a
single pass:
(*) There is an ordering on the subproblems, and a relation that shows how to solve a subproblem
given the answers to “smaller” subproblems, that is, subproblems that appear earlier in the
ordering.
In our case, each subproblem is solved using the relation
ax ϵ }
an expression which involves only smaller subproblems. How long does this step take? It requires
the predecessors of j to be known; for this the adjacency list of the reverse graph , constructible in
linear time, is handy. The computation of L(j) then takes time proportional to the indegree of j,
giving an overall running time linear in |E|. This is at most O( ), the maximum being when the
input array is sorted in increasing order. Thus the dynamic programming solution is both simple
and efficient.
There is one last issue to be cleared up: the L-values only tell us the length of the optimal
subsequence, so how do we recover the subsequence itself? This is easily managed with the same
bookkeeping device we used for shortest paths in Chapter 4. While computing L(j), we should also
note down prev(j), the next-to-last node on the longest path to j. The optimal subsequence can then
be reconstructed by following these backpointers.
Common Subproblems
Finding the right subproblem takes creativity and experimentation. But there are a few standard
choices that seem to arise repeatedly in dynamic programming
1. The input is … . . and a subproblem is ….
x x x x x x x x x x
The number of subproblem is therefore linear
2. The input is x … … x a y … . y . A subproblem is x … … x a y …. y .
x x x x x x x x x x
y y y y y y y y
The number of subproblem is O(mn)
3. The input is x … . . x a a su r sx x ……. x
x x x x x x x x x x
The number of subproblem is
4. The input is a rooted tree. A subproblem is a rooted subtree.
K(0) = 0
For w = 1 to W:
K(w) = max {K( )+v w w}
Return K(W)
This algorithm fills in a one-dimensional table of length W + 1, in left-to-right order. Each entry can
take up to O(n) time to compute, so the overall running time is O(nW). As always, there is an
underlying dag. Try constructing it, and you will be rewarded with a starting insight: this particular
variant of knapsack boils down to finding the longest path in a dag!
Memoization
In dynamic programming, we write out a recursive formula that expresses large problems in terms
of smaller ones and then use it to fill out a table of solution values in a bottom-up manner, from
smallest subproblem to largest.
The formula also suggests a recursive algorithm, but we saw earlier that naive recursion can be
terribly inefficient, because it solves the same subproblems over and over again. What about a more
intelligent recursive implementation, one that remembers its previous invocations and thereby
avoids repeating them?
On the knapsack problem (with repetitions), such an algorithm would use a hash table (recall
Section 1.5) to store the values of K(.) that had already been computed. At each recursive call
requesting some K(w), the algorithm would first check if the answer was already in the table and
then would proceed to its calculation only if it wasn't. This trick is called memoization:
A hash table, initially empty, holds values of K(w) indexed by w
function knapsack(w)
if w is in hash table: return K(w)
K(w) = max{knapsack(w w v w w}
insert K(w) into hash table, with key w
return K(w)
Since this algorithm never repeats a subproblem, its running time is O(nW), just like the dynamic
program. However, the constant factor in this big-O notation is substantially larger because of the
overhead of recursion.
In some cases, though, memoization pays off. Here's why: dynamic programming automatically
solves every subproblem that could conceivably be needed, while memoization only ends up solving
the ones that are actually used. For instance, suppose that W and all the weights w are multiples of
100. Then a subproblem K(w) is useless if 100 does not divide w. The memoized recursive
algorithm will never look at these extraneous table entries.
Shortest Paths
2 2
A A B B
1 1 A 4
5
S S5 15 T1 T
5
2 2 1 1
C C D3 D
3
We started this chapter with a dynamic programming algorithm for the elementary task of finding
the shortest path in a dag. We now turn to more sophisticated shortest-path problems and see how
these too can be accommodated by our powerful algorithmic technique.
st v st u uv }
s
Thus, using k gives us a shorter path from i to j if and only if
dist(i, k, ) + dist(k, j, ) < dist(i, j, )
in which case dist(i, j, k) should be updated accordingly.
Here is the Floyd – Warshall algorithm and as you can see, it takes | | time.
For i = 1 to n
For j = 1 to n
dist (i, j, 0) =
Independent sets in Trees
A subset of nodes S ⊂ V is an independent set of graph G = (V, E) if there are no edges between
them. For instance, the nodes {1, 5} form an independent set, but nodes {1, 4, 5} do not, because of
the edge between 4 and 5. The largest independent set is {2, 3, 6}. Like several other problems we
have seen in this chapter (knapsack, traveling salesman), finding the largest independent set in a
graph is believed to be intractable. However, when the graph happens to be a tree, the problem can
be solved in linear time, using dynamic programming. And what are the appropriate subproblems?
Already in the chain matrix multiplication problem we noticed that the layered structure of a tree
provides a natural definition of a subproblem as long as one node of the tree has been identified as a
root.
So here's the algorithm: Start by rooting the tree at any node r. Now, each node defines a subtree the
one hanging from it. This immediately suggests subproblems:
I(u) = Size of largest independent set of subtree hanging from u:
u ax { ∑ w ∑ w }
If the independent set includes u, then we get one point for it, but we aren't allowed to include the
children of u therefore we move on to the grandchildren. This is the first case in the formula. On the
other hand, if we don't include u, then we don't get a point for it, but we can move on to its children.
The number of subproblems is exactly the number of vertices. With a little care, the running time
can be made linear, O(|V| + |E|).
1 1 2 2
5 5 6 6
3 3 4 4
r r
u u
5
when he hits bottom."
CHAPTER
….George S. Patton
Hashing
Learning Objectives
After reading this chapter, you will know:
1. Hashing
2. Floading
3. Conflict Resolution Techniques
4. Double Hashing
5. Separate Chaining
6. Coalesced Chaining
Hashing
Hashing is a different kind of searching mechanism to search a table for a given key value: In
hashing, the record for a key value, “K”, is directly referred by calculating the address from the key
value.
Hash Function
Consider there are ‘n’ keys such that all key values are between ‘a’ and ‘b’. These ‘n’ element are to
be stored in a hash table of size ‘M’, where (M n) an elements with key value ‘K’ will be put in slot
‘j’ of the hash table, if j = h(k); ‘h’ is called the hash function. The domain of the hashing function
namely, the interval [a, b] is usually very wide. For example, h(K) may be defined as k mod M where
1 h(k) M for any integer ‘k’. Ideally, hash functions should result in a unique value when
applied to any key but such a hash function is not practically available.
An ideal hash function should distribute the keys uniformly over the range (0, M ). In other
words generating an address ‘x’ (where x M ) for a given key ‘k’ should be ( ). An hash
function may return same address for different keys, this situation is called Collision or Conflict.
There are many different hash functions and collision resolution techniques available, some of them
have been discussed in the next section.
Floading
Suppose we have p-digits key from which q digits address are to be generated. In this method, the
digits of a key are partitioned into groups of ‘q’ digits from the right. These groups are then added to
the right most ‘q’ digits and the sum is selected as the address. For example suppose from a ‘8’ digit
number, 39427829 and we have to generate a 3 digit number by partitioning this number into three
groups. 39 427 829
Adding 39, 427, 829 we get 1295. Selecting the last three digits, we get the desired address as 295.
Algorithm
Insert element into hash table using linear probe.
int insert Linear Probe (int k)
{
int i = 0, j;
j = hash (k);
do
{
if (T[j] = false)
{
A[j] = k;
T[j] = true;
return j;
}
i++
j = (j + i) %m
}
while (i < m)
return ( 1)
Algorithm
Searching an element from hash table using linear probe.
int search Linear Probe(int k)
{
int i = 0, j;
j = hash(k)
do
{
if (A[j] ==k)
{
return j;
}
i++;
j = (j+i)%m;
}
while ((T[j] = = True) && (i < m))
return ;
}
In quadratic probing let k is to be inserted in the hash table and h[k = j]. If A[j] is already occupied
then k has to be inserted at some other locations. In a quadratic probing, the location ‘j’, (j + 1),
(j + 4), (j + 9)& are examined to find out the first empty slot where ‘k’ may be inserted. Thus the
increment in this case i.e., i for i = 1, 2, 3, &
Double Hashing
This method requires two hashing functions f (k) and f (k). The function f (k) is used as a primary
hash function. If the address generated by primary hash function is already occupied by a key, the
function f (k) is evaluated. The second hash function is used to compute the increment to be added
to the address obtained by the first hash function in case of collision. Algorithm to store key in the
hash table using double hashing is shown below.
int store Double Hashing (int k)
{
int j, k, u, i = 0;
j = hash1(k);
u = hash2(k);
do
{
if (T[j] == false)
{
A[j] = k;
T[j] = true;
return j;
}
i++
j = ( j + i * u)%m;
}
while (i < m)
return – 1;
}
The search for an element in the array should follow the same path used to store the element.
Separate Chaining
In this method, linked list is maintained to store all elements that hash to the same address. Let the
hash table ‘A’ contain mentries or slots. Each slot contains a pointer to a link list and the list stores
the elements that hash to this slot. Each node in such a list is a self-referential structure containing a
key value and a pointer to the same structure. A typical ‘c’ declaration for a node may be done in the
following manner.
typed of struct node
{
int key;
struct node * next;
}
node;
Then the hash table ‘A’ containing m pointers may be defined as follows node * A [5 ]
while inserting a key ‘k’ using a hash function ‘h’ first h(k)is computed. Let h(k)be ‘i’, i m
then ‘k’ is inserted to the list pointed to by A[i] Therefore each slot in the hash table maintains a
separate chain (i.e. a linked list) to store all elements hashed to that slot. This is why this method is
known as ‘separate chaining’ method.
2
3 493
4
5
Coalesced Chaining
This technique is similar to linear probe method except that all keys that hash to the same address
are linked together. Each node in the hash table should have an additional field that will either
contain the address of the node holding a synonym or a null link. The node of the home element will
contain the address of the first synonym and this node in-turn will contain the address of the second
synonym and so on. A null link indicates the end of the chain.
Example: Consider a hash table with n buckets, where external (overflow) chaining is used to
resolve collisions. The hash function is such that the probability that a key value is hashed
to a particular bucket is . The hash table is initially empty and K distinct values are
inserted in the table.
(a) What is the probability that bucket number 1 is empty after the K insertion?
(b) What is the probability that no collision has occurred in any of the K insertion?
(c) What is the probability that the first collision occurs at the k insertion?
(A) 2 (C) 4
(B) 3 (D) 6
Solution: Given that all buckets are equally likely. Hash function distributes keys ideally.
(A) Probability that a key goes to a bucket ( )
Probability that a key does not go to a bucket ( )
Reference Books
2. Programming Languages
By Ravi Sethi
3. Introduction to Algorithms
By Corman, Rivest
5. ‘C’
By Kernighen and Ritchie