Design and Analysis of Algorithms
Design and Analysis of Algorithms
Text Books:
T1. Introduction to the Design and Analysis of Algorithms, Anany Levitin:, 2rd Edition, 2009.
Pearson.
T2. Computer Algorithms/C++, Ellis Horowitz, Sartaj Sahni and Rajasekaran, 2nd Edition, 2014,
Universities Press
Module 1
Module 1: INTRODUCTION
Computer programs would not exist without algorithms. And with computer applications becoming
indispensable in almost all aspects of our professional and personal lives, studying algorithms becomes a
necessity for more and more people. Studying algorithms is useful in developing analytical skills.
Notion of Algorithm:
2. Decide:
a) Ascertaining the Capabilities of the Computational Device:
Once understanding the problem completely, we need to ascertain the capabilities of the
computational device the algorithm is intended for.
Some of the algorithms follow RAM model; its central assumption is that instructions are
executed one after another, one operation at a time. Accordingly, algorithms designed to be
executed on such machines are called sequential algorithms.
Some computers can execute operations concurrently, i.e., in parallel. Algorithms that take
advantage of this capability are called parallel algorithms.
There are important problems, however, that are very complex by their nature, or have to process
huge volumes of data, or deal with applications where the time is critical. In such situations, it is
imperative to be aware of the speed and memory available on a particular computer system.
3. Designing an algorithm
Now design the algorithm and specify it using any of the following notations.
Methods of Specifying an Algorithm
Once designing an algorithm, it needs to be specified in some fashion. There exist different
notations, used for specifying algorithms.
Natural language
However, clear description of algorithms using natural language is difficult.
Pseudocode is a mixture of a natural language and programming language like
constructs. Pseudocode is usually more precise than natural language, and its usage often
yields more succinct algorithm descriptions.
Flowchart: a method of expressing an algorithm by a collection of connected geometric
shapes containing descriptions of the algorithm‘s steps. This representation technique has
proved to be inconvenient for all but very simple algorithms
5. Analyzing an Algorithm:
Analyzing an algorithm deals with the efficiency measurement.
There are two kinds of algorithm efficiency:
Time efficiency, indicates how fast the algorithm runs
Space efficiency, indicates how much extra memory it uses.
Another desirable characteristic of an algorithm is simplicity. Simplicity is about designing
algorithms that are simpler to understand and code.
Yet another desirable characteristic of an algorithm is generality. Generality is about designing
most general algorithms.
6. Coding an Algorithm:
The final step is to implement algorithms as computer programs. As a practical matter, the
validity of programs is established by testing.
Suppose we must device a program that sorts a collection of n>=1 element of type Type. A
simple solution could be as follows
From those elements that are currently unsorted, find the smallest and place it next in the
sorted list.
Although the above statements describe the sorting problem, it is not an algorithm. The above
statements does not tell us where and how the elements are initially stored or where we should
place the result. We assume that the elements are stored in an array, a, such that the i th position,
a[i], 1<=i<=n.
for(i=1;i<=n;i++){
examine a[i] to a[n] and suppose the smallest element is at a[j];
interchange a[i] and a[j];
}
The obvious observation is that, almost all algorithms run longer on larger inputs. For
example, it takes longer to sort larger arrays, multiply larger matrices, and so on. Therefore,
it is logical to investigate an algorithm’s efficiency as a function of some parameter n
indicating the algorithm’s input size.
Eg: for sorting, searching, and finding list‘s smallest/largest element and for problems
dealing with lists the input size would be size of the list. For evaluating polynomial of degree
n, the input size could be the degree or number of its coefficients.
Some problems require more than one parameter to indicate the size of their inputs. For some
problems it depends on the operations performed. For example a spell checking algorithm, if
it examines individual characters of its input, then the input size is measured by the number
of characters, if it works by processing words, then the input size is number of words.
For algorithms involving properties of numbers the size can be measured by the number b of
bits in the n‘s binary representation as b=log2n +1
We can simply use some standard unit of time measurement such as second, millisecond etc.
to measure the running time of a program implementing the algorithm. However, there are
obvious drawbacks to such an approach. Because, this unit is
dependent on the speed of a particular computer
dependent on the quality of a program implementing the algorithm
dependent on compiler used in generating the machine code
And difficulty of clocking the actual running time of the program.
We have to choose a metric that doesn‘t depend on these extraneous factors.
The following are the standard methods of computing the time efficiency of algorithms:
Operation Counts
Asymptotic notations(Mathematical analysis)
Operation Counts:
This approach deals with counting the number of times each of the algorithm‘s operations
is executed.
To do this,
1. Identify the most important operation of the algorithm, the basic operation, the
operation contributing the most to the total running time. It is usually the most time-
consuming operation in the algorithm‘s innermost loop.
Eg: (1) For sorting algorithms, basic operation is key comparison.
(2) Algorithms for mathematical problems: division is the most time consuming
operation, followed by multiplication and then addition and subtraction.
2. Count the number of times the algorithm‘s basic operation is executed on inputs of
size n.
Let cop be the execution time of an algorithm‘s basic operation on a particular computer
Let C(n) be the number of times this operation needs to be executed for this algorithm.
Then, the running time T(n) of a program implementing this algorithm on that computer
is
T (n) ≈ cop * C(n).
The above formula helps us to answer questions such as how much faster would an algorithm run
on a machine that is ten times faster than the one we have.
As an example consider C(n)=1/2*n*(n-1), how much longer will the algorithm run if we double
its input size? It runs four times longer, for all but very small values of n,
3. Orders of Growth:
The behavior of some algorithms change with increase in the large value of n.
The change in behavior of an algorithm as the value of n increases is called Order of
Growth.
This change in the behavior of the algorithm and the efficiency of the algorithm can be
analyzed by considering the highest order of n.
Basic Efficiency Classes
The time efficiencies of a large number of algorithms fall into only a few classes. These
classes are listed below in increasing order of their orders of growth, along with their
names
Class Name
1 constant
log n logarithmic
n linear
n log n n-log-n
n2 quadratic
n3 cubic
2n exponential
n! factorial
The function growing the slowest among these is the logarithmic function.
On the other end, exponential function 2n and the factorial function n! grow so fast
that their values become astronomically large even for rather small values of n. Both
are often referred to as ―exponential-growth functions‖ (or simply ―exponential‖)
Also, algorithms that require an exponential number of operations are practical for
solving only problems of very small sizes.
Another way to appreciate the qualitative difference among the orders of growth of
the functions is to consider how they react to, say, a twofold increase in the value of
their argument n.
The function log2n increases in value by just 1 (because, log22n = log22 + log2n =
1+ log2n)
The linear function n increases twofold
The nlog2n increases slightly more than twofold
The quadratic function n2 increase fourfold (because (2n)2 = 4n2) )
The cubic function n3 increase eightfold
The function 2n gets squared (because 22n = (2n)2 )
n! increases much more than that
The running time of this algorithm can be different for the same list size n. In the worst case,
when there are no matching elements or the first matching element happens to be the last one on
the list, the algorithm makes the largest number of key comparisons among all possible inputs of
size n.
Worst-case-efficiency:
The worst-case efficiency of an algorithm is its efficiency for the worst-case input of size
n, which is an input (or inputs) of size n for which the algorithm runs the longest among
all possible inputs of that size.
The way to determine the worst-case efficiency of an algorithm:
Analyze the algorithm to see what kind of inputs yield the largest value of the basic
operation‘s count C(n) among all possible inputs of size n and then compute this worst-
case value Cworst(n).
Eg: For sequential search, the worst case is when there are no matching elements or the
first matching element happens to be the last one on the list. Here, the algorithm makes
the largest number of key comparisons among all possible inputs of size n: Therefore,
Cworst(n)= n.
The worst-case analysis provides very important information about an algorithm‘s
efficiency by bounding its running time from above. In other words, it guarantees that for
any instance of size n, the running time will not exceed Cworst(n), its running time on the
worst-case inputs.
Best-case-efficiency:
The best-case efficiency of an algorithm is its efficiency for the best-case input of size n,
which is an input (or inputs) of size n for which the algorithm runs the fastest among all
possible inputs of that size.
We can analyze the best-case-efficiency as follows:
o Determine the kind of inputs for which the count C(n) will be the smallest among
all possible inputs of size n.
o Then ascertain the value of C(n) on these most convenient inputs.
For example, the best-case inputs for sequential search are lists of size n with their first
element equal to a search key; accordingly, Cbest(n) = 1 for this algorithm.
The analysis of the best-case efficiency is not nearly as important as that of the worst-
case efficiency. But it is not completely useless, either.
If the best-case-efficiency of an algorithm is unsatisfactory, we can immediately discard
it without further analysis.
Average-case-efficiency:
The Average-case efficiency of an algorithm is its efficiency for the ―random‖ input.
To analyze the algorithm‘s average-case-efficiency, we must make some assumptions
about possible inputs of size n.
Ex: consider again sequential search. The standard assumptions are that
a) the probability of a successful search is equal to p (0 ≤ p ≤ 1)
b) the probability of the first match occurring in the ith position of the list is the same for
every i.
Under these assumptions we can find the average number of key comparisons Cavg(n)
as follows.
i. In the case of a successful search, the probability of the first match occurring in the ith
position of the list is p/n for every i, and the number of comparisons made by the
algorithm in such a situation is i.
ii. In the case of an unsuccessful search, the number of comparisons will be n with the
probability of such a search being (1− p). Therefore,
if p = 1(the search must be successful), the average number of key comparisons made by
sequential search is (n + 1)/2; that is, the algorithm will inspect, on average, about half of
the list‘s elements.
If p = 0 (the search must be unsuccessful), the average number of key comparisons will
be n because the algorithm will inspect all n elements on all such inputs.
Investigation of the average-case efficiency is considerably more difficult than
investigation of the worst-case and best-case efficiencies.
Average-case efficiency cannot be obtained by taking the average of the worst-case and
the best-case efficiencies.
Amortized efficiency: It‘s another type of efficiency. It applies not to a single run of an
algorithm but rather to a sequence of operations performed on the same data structure. In some
situations a single operation can be expensive, but the total time for an entire sequence of n such
operations is always significantly better than the worst-case efficiency of that single operation
multiplied by n.
Points to remember:
Both time and space efficiencies are measured as functions of the algorithm‘s input size.
Time efficiency is measured by counting the number of times the algorithm‘s basic
operation is executed.
Space efficiency is measured by counting the number of extra memory units consumed
by the algorithm.
The efficiencies of some algorithms may differ significantly for inputs of the same size.
For such algorithms, we need to distinguish between the worst-case, average-case, and
best-case efficiencies.
The framework‘s primary interest lies in the order of growth of the algorithm‘s running
time as its input size goes to infinity.
Let CP= space required for the static part of the program
SP= space required for the dynamic part of the program
Then, the total space required for a program P, is S(P)=CP+SP
Examples:
Iterative type algorithms:
1) To add three integers :
Add(a,b,c)
return a+b+c
sumsum+x[i]
return sum
Recursive algorithms:
3) Add(x,n) // x is an array of integer elements
// n is the size of an array
Return Add(x,n-1)+x[n]
Here stack is used for recursion. The internal stack for recursion includes space for
formal parameters, local variables and return address.
Here, each call to function Add requires atleast 3 words:
Space for n values
Space for return address
Space for pointer to x[]
Depth of recursion: n+1 ( n times call + 1 time return call )
Therefore, the total space: >=3(n+1)
Time Complexity:
The time T(p) taken by a program P is the sum of the compile time and runtime. The compile
time does not depend on the instance characteristics. Also a compiled program can run several
times without recompilation. Hence we consider only the runtime of a program denoted by
tP(instance characteristics).
A Program step is loosely defined as a syntactically or semantically meaningful segment of a
program that has an execution time that is independent of the instance characteristics.
The number of steps any program statement is assigned depends on the kind of statement.
For example, comments and declarative statements such as int,struct,#include,class etc
count as zero steps.
Assignment statements which does not involve any calls to other programs is counted as
one step
For the iterative statements like for,while etc we consider step count only for control part
of the statement.
Control part for while and for statements have the following forms:
for(i=<expr>;i<=<expr1>;i++)
while (<expr>)
We can determine the number of steps needed by a program to solve a particular problem
instance in one of the 2 ways
1. Introduce a variable called count into the program. This is a global variable initialized to
zero. Statements to increment count by appropriate amount are introduced into the
program. Each time a statement in the program is executed count is incremented.
2. Build a table in which we list the total number of steps contributed by each statement.
{
float s=0.0;
count++;
for(int i=1;i<=n;i++)
{
count++;
s+=a[i];count++;
}
count++;
count++;
return s;
}
Since we are interested in knowing only the change in count variable, the above program
can be simplified as follows
disappear.
So the step count of RSum is 2n+2.
Asymptotic Notations:
The efficiency analysis concentrates on the order of growth of an algorithm‘s basic operation
count. To compare and rank such orders of growth, the following three asymptotic notations
are used
O(big oh)
Ω(big omega)
Θ(big theta).
Let t (n) and g(n) can be any nonnegative functions defined on the set of natural numbers.
t(n) will be an algorithm‘s running time and g(n) will be some simple function to compare
the count with.
O- Notation:
O(g(n)) is the set of all functions with a lower or same order of growth as g(n) ( to within a
constant multiple, as n goes to infinity).
Definition: A function t(n) is said to be in O(g(n)), denoted t (n) O(g(n)), if t (n) is bounded
above by some constant multiple of g(n) for all large n, i.e., if there exist some positive
constant c and some nonnegative integer n0 such that
t (n) ≤ c.g(n) for all n ≥ n0.
Where n is the size of input
g(n) is a function
( t(n) is the time efficiency of an algorithm)
The definition is illustrated in the following Figure 2.1 where, n is extended to be a real
number.
Here, a graph of time, t versus input, n is plotted for the functions t(n) and c.g(n)
The function t(n) lies below c.g(n)
Big-O is a method of expressing the upper bound of an algorithm‘s running time. And it is a
measure of the longest amount of time it could take for the algorithm to complete.
Examples: the following assertions are all true:
n O(n2), 100n + 5 O(n2), 1 n(n-1) O(n2).
2
Let us formally prove one of the assertions made: 100n + 5 O(n2).
(i) 100n + 5 ≤ 100n + n (for all n ≥ 5) = 101n ≤ 101n2.
Thus, we can take c=101 and n0=5.
(ii) We could also reason that,
100n + 5 ≤ 100n + 5n (for all n ≥ 1) = 105n ≤ 105n2
with c = 105 and n0 = 1.
On the other hand,
n3 O(n2), 0.00001n3 O(n2), n4+n+1 O(n2)
3 3
Indeed, the functions n and 0.00001n are both cubic and hence have a higher order of
growth than n2, and so has the fourth-degree polynomial n4 + n + 1.
Ω – Notation:
Ω(g(n)), stands for the set of all functions with a higher or same order of growth as g(n).
Definition: A function t (n) is said to be in Ω(g(n)), denoted t(n) Ω(g(n)), if t(n) is bounded
below by some positive constant multiple of g(n) for all large n, i.e., if there exist some
positive constant c and some nonnegative integer n0 such that
t (n) ≥ cg(n) for all n ≥ n0.
So, if we draw a graph t(n) and c.g(n) versus n, the graph of t(n) lies above the graph of
c.g(n) for sufficiently large value of n as shown below:
Examples:
n3 Ω(n2), 1 n(n-1) Ω(n2), but 100n+5 Ω(n2)
2
Dept. of CSE, SJEC Page 14
Design and Analysis of Algorithms Module 1
Θ-Notation:
Θ(g(n)) is the set of all functions that have the same order of growth as g(n).
Definition: A function t(n) is said to be in Θ(g(n)), denoted t(n) Θ(g(n)), if t(n) is bounded
both above and below by some positive constant multiples of g(n) for all large n, i.e., if there
exist some positive constants c1 and c2 and some nonnegative integer n0 such that
c2g(n) ≤ t (n) ≤ c1g(n) for all n ≥ n0.
So, if we draw a graph t(n), c1.g(n) and c2.g(n) versus n, the graph of t(n) lies above the graph
of c2.g(n) and lies below the graph c1.g(n) for sufficiently large value of n as shown below:
Examples:
(1) Every quadratic function an2+bn+c with a>0 is in Θ(n2).
(2) Let us prove that 1 n(n-1) Θ(n2).
2
First, we prove the right inequality (upper bound):
PROOF The proof extends to orders of growth the following simple fact about four arbitrary
real numbers a1, b1, a2, b2: if a1 ≤ b1 and a2 ≤ b2, then a1 + a2 ≤ 2 max{b1, b2}.
Since t1(n) O(g1(n)), there exist some positive constant c1 and some nonnegative integer n1
such that
t1(n) ≤ c1g1(n) for all n ≥ n1.
Similarly, since t2(n) O(g2(n)),
t2(n) ≤ c2g2(n) for all n ≥ n2.
Let us denote c3 = max{c1, c2} and consider n ≥ max{n1, n2} so that we can use both inequalities.
Adding them yields the following:
t1(n) + t2(n) ≤ c1g1(n) + c2g2(n)
≤ c3g1(n) + c3g2(n) = c3[g1(n) + g2(n)]
≤ c32 max{g1(n), g2(n)}.
Hence, t1(n) + t2(n) O(max{g1(n), g2(n)}), with the constants c and n0 required by the O
definition being 2c3 = 2 max{c1, c2} and max{n1, n2}, respectively.
It implies that the algorithm‘s overall efficiency is determined by the part with a higher order of
growth, i.e., its least efficient part:
| t1(n) O(g1(n)) | t1(n) + t2(n) O(max{g1(n), g2(n)})
| t2(n) O(g2(n)) |
For example, If a sorting algorithm used in the first part makes no more than ½ n(n − 1)
comparisons (and hence is in O(n2)) the second part makes no more than n − 1
comparisons (and hence is in O(n)), then the efficiency of the entire algorithm
will be in O(max{n2, n}) = O(n2).
Note :
first two cases mean that t(n) O(g(n)),
the last two mean that t(n) Ω(g(n)),
the second case means that t(n) Θ(g(n)).
The limit-based approach is often more convenient than the one based on the definitions because
it can take advantage of the powerful calculus techniques developed for computing limits, such
as L‘H ˆ opital‘s rule
Since the limit is equal to a positive constant, the functions have the same order of growth or,
symbolically, ½ n(n − 1) Θ(n2).
Since the limit is equal to zero, log2 n has a smaller order of growth than n.
Thus, though 2n grows very fast, n! grows still faster. We can write symbolically that n! Ω (2n).
Summation formulas:
EXAMPLE 1: Consider the problem of finding the value of the largest element in a list of n
numbers in an array.
Pseudocode for solving this problem:
ALGORITHM MaxElement(A[0..n − 1])
//Determines the value of the largest element in a given array
//Input: An array A[0..n − 1] of real numbers
//Output: The value of the largest element in A
maxval ←A[0]
for i ←1 to n − 1 do
if A[i]>maxval
maxval←A[i]
return maxval
Analysis:
o Parameter to be considered is n (size of input).
o The operations that are going to be executed most often are in the algorithm‘s for loop.
There are two operations in the loop‘s body:
the comparison A[i]> maxval
the assignment maxval←A[i].
Since the comparison is executed on each repetition of the loop, we should consider it to
be the algorithm‘s basic operation.
o There is no need to distinguish among the worst, average, and best cases here since the
number of comparisons will be the same for all arrays of size n.
o Let C(n) denote the no. of times comparisons made.
The algorithm makes one comparison on each execution of the loop
And this is repeated once for each value of the loop‘s variable i
The variable i is within the bounds 1 and n − 1.
Therefore, we get
n-1
C(n) = 1
i=1
u
i.e. C(n) = n-1 [ based on the summation formula, 1 = u-m+1, m<=u ]
im
C(n) Θ(n)
EXAMPLE 2: element uniqueness problem: check whether all the elements in a given array of
n elements are distinct.
//Output: Returns ―true‖ if all the elements in A are distinct and ―false‖ otherwise
for i ←0 to n − 2 do
for j ←i + 1 to n − 1 do
if A[i]= A[j ] return false
return true
Analysis:
o Parameter to be considered is n (size of input).
o Since the innermost loop contains a single operation (the comparison of two elements),
we should consider it as the algorithm‘s basic operation.
o The number of element comparisons depends not only on n but also on whether there are
equal elements in the array and, if there are, which array positions they occupy.
We will limit our investigation to the worst case only. An inspection of the innermost
loop reveals that there are two kinds of worst-case inputs:
arrays with no equal elements
Arrays in which the last two elements are the only pair of equal elements.
o Here, one comparison is made for each repetition of the innermost loop, i.e., for each
value of the loop variable j between its limits i + 1 and n − 1;
This is repeated for each value of the outer loop, i.e., for each value of the loop variable i
between its limits 0 and n − 2.
Accordingly, we get
n2 n 1
Cworst(n) = 1
i 0 j i 1
n2
[( n 1) (i 1) 1]
u
=
i 0
[based on summation formula 1 = l-u+1, l<=u]
i l
n2
= (n 1 i)
i 0
n2 n2
= ( n 1) - i
i 0 i 0
n2
(n 2)(n 1) n( n 1)
n
= (n-1) 1 -
i 0 2
[ i=i 1 2
]
(n 2)(n 1)
= (n-1)2 -
2
( n 1) n 1
= n2 Θ(n2)
2 2
EXAMPLE 3: Given two n × n matrices A and B, find the time efficiency of the definition-
based algorithm for computing their product C = AB.
Pseudocode:
ALGORITHM MatrixMultiplication(A[0..n − 1, 0..n − 1], B[0..n − 1, 0..n − 1])
//Multiplies two square matrices of order n by the definition-based algorithm
//Input: Two n × n matrices A and B
//Output: Matrix C = AB
for i ←0 to n − 1 do
Dept. of CSE, SJEC Page 19
Design and Analysis of Algorithms Module 1
for j ←0 to n − 1 do
C[i, j ]←0.0
for k←0 to n − 1 do
C[i, j ]←C[i, j ]+ A[i, k] * B[k, j]
return C
Analysis:
o Parameter to be considered is n (size of input).
o There are two arithmetical operations in the innermost loop here: multiplication and
addition.
We consider multiplication as the basic operation.
o Let M(n) be the sum for the total number of multiplications executed by the algorithm.
o There is just one multiplication executed on each repetition of the algorithm‘s innermost
loop, which is governed by the variable k ranging from the lower bound 0 to the upper
bound n − 1.
o Therefore,
the number of multiplications made for every pair of specific values of variables i
n 1
and j is 1
k 0
the total number of multiplications M(n) is expressed by the following triple sum:
n 1 n 1 n 1
M(n) =
i 0
1
j 0 k 0
n 1 n 1
= n
i 0 j 0
n 1
= n
i 0
2
= n3
o now, the running time of the algorithm on a particular machine can be estimated as
T (n) ≈ cmM(n) = cmn3,
where cm is the time of one multiplication on the machine in question.
We would get a more accurate estimate if we took into account the time spent on the
additions, too:
T (n) ≈ cmM(n) + caA(n) = cmn3 + can3 = (cm+ ca)n3,
where ca is the time of one addition.
EXAMPLE 4: Find the number of binary digits in the binary representation of a positive
decimal integer.
Pseudocode:
ALGORITHM Binary(n)
//Input: A positive decimal integer n
//Output: The number of binary digits in n‘s binary representation
count ←1
while n > 1 do
count ←count + 1
n← n / 2
return count
o the most frequently executed operation here is not inside the while loop but rather the
comparison n > 1 that determines whether the loop‘s body will be executed.
o loop variable takes on only a few values between its lower and upper limits; therefore, we
have to use an alternative way of computing the number of times the loop is executed.
Since the value of n is about halved on each repetition of the loop, the answer should be
about log2n.
o The exact formula for the number of times the comparison n>1 will be executed is
actually + log2 n + 1
EXAMPLE 1: Compute the factorial function F(n) = n! for an arbitrary nonnegative integer n.
Analysis:
o Parameter to be considered is n (size of input).
o The basic operation of the algorithm is multiplication
o The total number of executions made by the basic operation is denoted by M(n).
Thus, we succeeded in setting up the recurrence relation and initial condition for the
algorithm‘s number of multiplications M(n):
M(n) = M(n − 1) + 1 for n > 0
M(0) = 0.
EXAMPLE 2: Tower of Hanoi puzzle. In this puzzle, we have n disks of different sizes that can
slide onto any of three pegs. Initially, all the disks are on the first peg in order of size, the largest
on the bottom and the smallest on top. The goal is to move all the disks to the third peg, using the
second one as an auxiliary, if necessary. We can move only one disk at a time, and it is forbidden
to place a larger disk on top of a smaller one.
The problem has an elegant recursive solution, which is illustrated in Figure 2.4.
Analysis:
o Parameter to be considered is n (no.of disks).
o The basic operation is movement of disk.
o The number of moves M(n) depends on n only, and
we get the following recurrence equation:
M(n) = M(n − 1) + 1+ M(n − 1) for n > 1.
M(1) = 1 initial condition :
1(2 i 1)
= 2iM(n − i) + 2i – 1 here, a=1, r=2, n=i , i.e.=
2 1
Since the initial condition is specified for n = 1, which is achieved for i = n − 1, we get
the following solution:
M(n) = 2n−1M(n − (n − 1)) + 2n−1 − 1
= 2n−1M(1) + 2n−1 − 1
= 2n−1+ 2n−1 – 1
= 2n − 1.
Thus, we have an exponential algorithm and the complexity of Towers of Hanoi function
is given by t(n) Θ(2n-1) Θ(2n)
Note:
o When a recursive algorithm makes more than a single call to itself, it can be useful for
analysis purposes to construct a tree of its recursive calls.
In this tree, nodes correspond to recursive calls, and we can label them with the value of
the parameter (or, more generally, parameters) of the calls.
o For the Tower of Hanoi example, the tree is given below.
o By counting the number of nodes in the tree, we can get the total number of calls made
by the Tower of Hanoi algorithm:
n 1
C(n) = 2 l (where l is the level in the tree in Figure 2.5) = 2n − 1.
l 0
Analysis:
o Parameter to be considered is n (size of input).
o The basic operation is
BinRec( n / 2 ) + 1
Sorting
The sorting problem is to rearrange the items of a given list in ascending order.
Further, sorting makes many questions about the list easier to answer. The most
important of them is searching. Also it is used as an auxiliary step in several important
algorithms in other areas, e.g., geometric algorithms; greedy approach requires a sorted
input.
String Processing
A string is a sequence of characters from an alphabet.
Strings of particular interest are
text strings, which comprise letters, numbers, and special characters;
bit strings, which comprise zeros and ones;
Gene sequences, which can be modeled by strings of characters from the four-
character alphabet {A, C, G, T}.
String-processing algorithms have been found important for computer science for a long
time in conjunction with computer languages and compiling issues.
One particular problem: string matching.
Several algorithms that exploit the special nature of this type of searching have been
invented.
Graph Problems
A graph can be thought of as a collection of points called vertices, some of which are
connected by line segments called edges. Graphs can be used for modeling a wide variety
of applications, including transportation, communication, social and economic networks,
project scheduling, and games
Basic graph algorithms include
graph-traversal algorithms
shortest-path algorithms
topological sorting for graphs with directed edges
Some graph problems are computationally very hard; the most well-known examples are
the traveling salesman problem and the graph-coloring problem.
The traveling salesman problem (TSP) is the problem of finding the shortest tour
through n cities that visits every city exactly once. In addition to obvious applications
involving route planning, it arises in such modern applications as circuit board and
VLSI chip fabrication, X-ray crystallography, and genetic engineering.
The graph-coloring problem seeks to assign the smallest number of colors to the
vertices of a graph so that no two adjacent vertices are the same color. This problem
arises in several applications, such as event scheduling: if the events are represented
by vertices that are connected by an edge if and only if the corresponding events
cannot be scheduled at the same time, a solution to the graph-coloring problem yields
an optimal schedule.
Combinatorial Problems
These are problems that ask, explicitly or implicitly, to find a combinatorial object—such
as a permutation, a combination, or a subset—that satisfies certain constraints. A desired
combinatorial object may also be required to have some additional property such as a
maximum value or a minimum cost.
From a more abstract perspective, the traveling salesman problem and the graph coloring
problem are examples of combinatorial problems.
Combinatorial problems are the most difficult problems in computing, from both a
theoretical and practical standpoint.
Note:
Among several ways to classify algorithms, the two principal alternatives are:
1. According to types of problems they solve.
2. According to underlying design techniques they are based upon.
Algorithms operate on data. This makes the issue of data structuring critical for efficient
algorithmic problem solving. The most important elementary data structures are the array
and the linked list. They are used for representing more abstract data structures such as the
list, the stack, the queue, the graph (via its adjacency matrix or adjacency lists), the binary
tree, and the set.
To access a particular node, we start with the first node and traverse the pointer chain
until the particular node is reached.
Doubly Linked List in which every node except the first and the last contains pointers to
both its successor and its predecessor.
Arrays and Linked list are principal choices in representing more abstract data structure
called a linear list
Basic operations performed on a Linear List are insertion, deletion and searching an
element.
Example for Lists are Stacks and Queues.
Stack is a list in which insertion and deletions can be done only at one end. This end is
called as the Top. Hence stack is commonly referred to LIFO(Last in First Out).
A Queue is a list in which elements are inserted from one end called the rear(enqueue)
and deleted from the other end called the front(dequeue).
A priority queue is a collection of data items from a totally ordered universe(integers and
real numbers)
The main operations on a priority queue are finding the largest element,deleting the
largest element and adding a new element.
Graphs
A graph G=(V,E) is defined by a pair of two sets : a finite set V of items called vertices and
a set E of pairs of these lines called edges.
If these pairs of vertices are unordered i.e a pair of vertices (u,v) is same as vertices(v,u)
then we say that vertices u and v are adjacent to each other and that they are connected by
undirected edge(u,v)
If a pair of vertices(u,v) is not the same as the pair (v,u) we say that the edge (u,v) is
directed from the vertex u, called the edge‘s tail to the vertex v called the edge‘s head.
E={(a,c),(b,c),(b,f),(c,e),(d,a),(d,e),(e,c),(e,f)}
Graph Representations:graphs for computer algorithms can be represented in two
principal ways: the adjacency matrix and adjacency lists.
The adjacency matrix of a graph with n vertices is a n by n Boolean matrix with one row
and one coloumn for each the graph vertices,in which the element in the ith row and the
jth coloumn is equal to 1 if there is an edge from the ith vertex to the jth vertex, and
equal to 0 if there is no such edge .
Adjacency list of a graph or a diagraph is a collection of linked list,one for each vertex
that contain all the vertices adjacent to the lists vertex.
Weighted Graphs:
A weighted graph is a graph with numbers assigned to its edges. These numbers are
called weights or costs as shown below in figure (a).
If a weighted graph is represented by its adjacency matrix then its element A[i,j] will
contain the weight of the edge from the ith to the jth vertex if there is such an edge and a
special symbol ……if there is no such edge. This matrix is called the weighted matrix or
cost matrix as shown below in figure (b).
Adjacency lists for a weighted graph must include in their nodes the name of the adjacent
vertex and also the weight of the corresponding edge as shown in figure (c).
Trees:
A tree is a connected acyclic graph. (Figure (a) below). A graph that has no cycles but is not
necessarily connected is called a forest. Each of the connected components is a tree.(figure
b).The number of edges in a tree is always one less than the number of vertices.|E|=|V|-1.
Rooted Trees:
For every 2 vertices in a tree, there always exists one simple path from one of these vertices to
another. Any free tree can be converted to a rooted tree by choosing an arbitrary vertex as the
root of the rooted tree. The root is placed at the level 0 of the tree, the vertices adjacent to the
root below it(level 1),the vertices two edges apart from the root below that(level 2) and so figure
below shows transformation of a free tree to a rooted tree.
For every vertex v in a tree T, all the vertices on the simple path from the root to that
vertex are called ancestors of v. If (u,v) is the last edge of the simple path from the root to
vertex v ,u is said to be the parent of v and v is called a child of u,
Vertices who have the same parent are called siblings.
A vertex with no children is called the leaf. A vertex with atleast one child is called
parental. All vertices for which a vertex v is an ancestor are said to be descendants of v.
The depth of a vertex v is the length of the simple path from the root to v. The height of a
tree is the length of the longest simple path from the root to a leaf. Example Depth of the
vertex c in the tree in figure 9 is 2 and height of the tree is 3.
Ordered Trees:
An ordered tree is a rooted tree in which all the children of each vertex are ordered. A binary tree
can be defined as an ordered tree in which very vertex has no more than 2 children and each
child is designates as either the left child or the right child. In a binary search tree the nodes to
the left of the parent node has to be smaller than the parent node and the nodes to the right has to
larger than the parent node (figure a, b).
Figure below shows the standard implementation of binary search tree of figure (b) above.
Below figure (a) shows the first child-next sibling representation of the graph and figure (b)
shows its binary representation.
min←i
for j ←i + 1 to n − 1 do
if A[j ]<A[min] min←j
swap A[i] and A[min]
Example: the action of the algorithm on the list 89, 45, 68, 90, 29, 34, 17 is illustrated below:
| 89 45 68 90 29 34 17
17 | 45 68 90 29 34 89
17 29 | 68 90 45 34 89
17 29 34 | 90 45 68 89
17 29 34 45 | 90 68 89
17 29 34 45 68 | 90 89
17 29 34 45 68 89 | 90
= (n 1) - i
i 0 i 0
n2
1 - (n 2)(2 n 1) n( n 1)
n
= (n-1) [ i= ]
i 0 i 1 2
(n 2)(n 1)
= (n-1)2 -
2
( n 1) n 1
= n2 Θ(n2)
2 2
o Thus,
Selection sort is a Θ(n2) algorithm on all inputs.
The number of key swaps is only Θ(n), or, more precisely, n – 1. This property
distinguishes selection sort from many other sorting algorithms.
Bubble Sort:
Bubble sort makes use of Brute-force strategy by comparing adjacent elements of the list and
exchanging them if they are out of order. By doing it repeatedly, we end up ―bubbling up‖
the largest element to the last position on the list.
Dept. of CSE, SJEC Page 33
Design and Analysis of Algorithms Module 1
Pseudocode:
ALGORITHM BubbleSort(A[0..n − 1])
//Sorts a given array by bubble sort
//Input: An array A[0..n − 1] of orderable elements
//Output: Array A[0..n − 1] sorted in ascending order
for i ←0 to n − 2 do
for j ←0 to n − 2 − i do
if A[j + 1]<A[j ] swap A[j ] and A[j + 1]
Example: the action of the algorithm on the list 89, 45, 68, 90, 29, 34, 17 is illustrated below:
89
?
45 68 90 29 34 17
45 89 68
?
90 29 34 17
45 68 89 90 29
? ?
34 17
45 68 89 29 90 34
?
17
45 68 89 29 34 90
?
17
45 68 89 29 34 17 | 90
45
?
68
?
89
?
29 34 17 | 90
45 68 29 89 34
?
17 | 90
45 68 29 34 89 17 | 90
?
45 68 29 34 17 | 89 90
…………………………… etc
Analysis:
o Parameter to be considered is n (size of input).
o The basic operation is the key comparison A[j+1 ]<A[j].
o The number of key comparisons for the bubble-sort is the same for all arrays of size n;
and it is given below:
n2 n 2 i
C(n) =
i 0
1
j 0
n2
= [( n 2 i) 0 1]
i 0
n2
= (n 1 i)
i 0
( n 1) n
= Θ(n2) [ same as selection sort analysis ]
2
The number of key swaps, however, depends on the input. In the worst case of decreasing
arrays, it is the same as the number of key comparisons:
( n 1) n
Sworst(n) = C(n) = Θ(n2)
2
Reference Questions:
1. What is an algorithm? What are the essential properties of an algorithm? Explain
2. Explain in brief, the basic asymptotic efficiency classes
3. What are space and time complexities of an algorithm?
4. Explain how you would compute the time complexity of non-recursive algorithms.
5. Explain how you would compute the time complexity of recursive algorithms.
6. What are asymptotic notations? What is their significance? Explain.
7. Explain different asymptotic notations used to analyze an algorithm
8. Explain asymptotic notations and efficiency classes.
9. Explain the general framework of algorithms. Explain best-case, worst-case and average-
case efficiencies with an example.
10. Define the asymptotic notations for the best-case, worst-case and average-case
efficiencies with an example.
11. With at least two examples each, Define and explain O, Θ and
12. If t1(n) O(g1(n)) and t2(n) O(g2(n)), then prove that, t1(n)+t2(n) O(max{g1(n), g2(n)}).
13. Explain the method of comparing orders of growth of two functions using limits. Also
compare the following functions:
(i) log2n and n (ii) (log2n)2 and log2n2
14. Calculate the efficiency of an algorithm for finding the largest element in an array.
15. Write an algorithm to perform matrix multiplication and calculate its efficiency.
16. Describe the non-recursive algorithm for finding the binary representation of a given
decimal number. Also calculate its efficiency.
17. For the following algorithms, indicate:
(a) Natural size metric for the inputs (b) Basic operations (c) Whether the basic
operation counts can be different for inputs of the same size
(i) Computing sum of ‗n‘ numbers
(ii) Computing xn
(iii) Finding largest element in a list of ‗n‘ numbers
(iv) Euclid‘s algorithm
18. Prove that (i) ½ n(n-1) Θ(n2) (ii) 3n3+2n2 O(n3) (iii) n! (2n)
19. Indicate whether the first function in each of the following pair, has smaller, same or
larger order of growth than the second function.
(i) n(n+1) and 2000n2 (ii) 100 n2 and 0.01 n3 (iii) log2n and ln n
(iv) 2n-1 and 2n (v) log2n and log2n2 (vi) (n-1)! And n!
20. Consider the following algorithm
Algorithm X(int N)
{
int P;
for i =0 to N
{
Printf(―\n %d\t * \t %d = %d ―, N,i,P);
P=P+N;
}
}
(i) What does this algorithm compute?
(ii) What is the basic operation?
(iii) How many times basic operation is executed?
*******************************************************************
Dept. of CSE, SJEC Page 36
Copy protected with Online-PDF-No-Copy.com