Unit 1
Unit 1
3003
COURSE OBJECTIVES:
To understand the usage of algorithms in computing
To learn and use hierarchical data structures and its operations
To learn the usage of graphs and its applications
To select and design data structures and algorithms that is appropriate for problems
To study about NP Completeness of problems.
TOTAL : 45 PERIODS
UNIT I ROLE OF ALGORITHMS IN COMPUTING &
COMPLEXITY ANALYSIS
1.1 ALGORITHM:
NOTION OF AN ALGORITHM
Problem to be solved
Algorithm
It is a step by step procedure with the input to solve the problem in a finite amount of time
to obtain the required output.
Characteristics of an algorithm:
Input: Zero / more quantities are externally supplied.
Output: At least one quantity is produced.
Definiteness: Each instruction is clear.
Finiteness: If the instructions of an algorithm is traced then for all cases the algorithm must
terminates after a finite number of steps.
Efficiency: Every instruction must be very basic and runs in short time.
Steps for writing an algorithm:
1. An algorithm is a procedure. It has two parts; the first part is head and the second part is
body.
2. The Head section consists of keyword Algorithm and Name of the algorithm with
parameter list. E.g. Algorithm name1(p1, p2,…,p3)
The head section also has the following:
//Problem Description:
//Input:
//Output:
3. In the body of an algorithm various programming constructs like if, for, while and some
statements like assignments are used.
4. The compound statements may be enclosed with { and } brackets. if, for, while can be
closed by endif, endfor, endwhile respectively. Proper indention is must for block.
5. Comments are written using // at the beginning.
6. The identifier should begin by a letter and not by digit. It contains alpha numeric letters
after first letter. No need to mention data types.
7. The left arrow “←” used as assignment operator. E.g. v←10
8. Boolean operators (TRUE, FALSE), Logical operators (AND, OR, NOT) and Relational
operators (<,<=, >, >=,=, ≠, <>) are also used.
9. Input and Output can be done using read and write.
10. Array[], if then else condition, branch and loop can be also used in algorithm.
Example:
The greatest common divisor(GCD) of two nonnegative integers m and n (not-both-zero),
denoted gcd(m, n), is defined as the largest integer that divides both m and n evenly, i.e., with a
remainder of zero.
Euclid’s algorithm is based on applying repeatedly the equality gcd(m, n) = gcd(n, m mod n),
where m mod n is the remainder of the division of m by n, until m mod n is equal to 0. Since gcd(m,
0) = m, the last value of m is also the greatest common divisor of the initial m and n.
gcd(60, 24) can be computed as follows:gcd(60, 24) = gcd(24, 12) = gcd(12, 0) = 12.
Pseudocode and flowchart are the two options that are most widely used nowadays for specifying
algorithms.
a. Natural Language
It is very simple and easy to specify an algorithm using natural language. But many times
specification of algorithm by using natural language is not clear and thereby we get brief
specification.
Example: An algorithm to perform addition of two numbers.
Step 1: Read the first number, say a.
Step 2: Read the first number, say b.
Step 3: Add the above two numbers and store the result in c.
Step 4: Display the result from c.
Such a specification creates difficulty while actually implementing it. Hence many programmers
prefer to have specification of algorithm by means of Pseudocode.
b. Pseudocode
Pseudocode is a mixture of a natural language and programming language constructs.
Pseudocode is usually more precise than natural language.
For Assignment operation left arrow “←”, for comments two slashes “//”,if condition, for,
while loops are used.
ALGORITHM Sum(a,b)
//Problem Description: This algorithm performs addition of two numbers
//Input: Two integers a and b
//Output: Addition of two integers
c←a+b
return c
This specification is more useful for implementation of any language.
c. Flowchart
In the earlier days of computing, the dominant method for specifying algorithms was a flowchart,
this representation technique has proved to be inconvenient.
Flowchart is a graphical representation of an algorithm. It is a a method of expressing an algorithm
by a collection of connected geometric shapes containing descriptions of the algorithm’s steps.
Symbols Example: Addition of a and b
Transition / Assignment
Input the value of a
Condition / Decision
Display the value of c
Flow connectivity
FIGURE 1.4 Flowchart symbols and Example for two integer addition.
Once an algorithm has been specified then its correctness must be proved.
An algorithm must yields a required result for every legitimate input in a finite amount of
time.
For example, the correctness of Euclid’s algorithm for computing the greatest common
divisor stems from the correctness of the equality gcd(m, n) = gcd(n, m mod n).
A common technique for proving correctness is to use mathematical induction because an
algorithm’s iterations provide a natural sequence of steps needed for such proofs.
The notion of correctness for approximation algorithms is less straightforward than it is for
exact algorithms. The error produced by the algorithm should not exceed a predefined limit.
Algorithms are just like a technology. We all use latest and greatest processors but we need to run
implementations of good algorithms on that computer in order to properly take benefits of our
money that we spent to have the latest processor. Let’s make this example more concrete by pitting
a faster computer(computer A) running a sorting algorithm whose running time on n values grows
like n2 against a slower computer (computer B) running asorting algorithm whose running time
grows like n lg n. They eachmust sort an array of 10 million numbers. Suppose that computer A
executes 10 billion instructions per second (faster than anysingle sequential computer at the time of
this writing) and computer B executes only 10 million instructions per second, so that computer A
is1000 times faster than computer B in raw computing power. To makethe difference even more
dramatic, suppose that the world’s craftiestprogrammer codes in machine language for computer
A, and the resulting code requires 2n2 instructions to sort n numbers. Suppose furtherthat just an
average programmer writes for computer B, using a high- level language with an inefficient
compiler, with the resulting code taking 50n lg n instructions.
Time taken=
Eg4: Multiplication
Basic Operation: Repeated Addition
3*2=2+2+2/3+3
(iii) Orders of Growth
A difference in running times on small inputs is not what really distinguishes efficient
algorithms from inefficient ones.
For example, the greatest common divisor of two small numbers, it is not immediately clear
how much more efficient Euclid’s algorithm is compared to the other algorithms, the
difference in algorithm efficiencies becomes clear for larger numbers only.
For large values of n, it is the function’s order of growth that counts just like the Table 1.1,
which contains values of a few functions particularly important for analysis of algorithms.
TABLE 1.1 Values (approximate) of several functions important for analysis of algorithms
n √ log2n n n log2n n2 n3 2n n!
1 1 0 1 0 1 1 2 1
2 1.4 1 2 2 4 4 4 2
4 2 2 4 8 16 64 16 24
8 2.8 3 8 2.4•101 64 5.1•102 2.6•102 4.0•104
10 3.2 3.3 10 3.3•101 102 103 103 3.6•106
16 4 4 16 6.4•101 2.6•102 4.1•103 6.5•104 2.1•1013
102 10 6.6 102 6.6•102 104 106 1.3•1030 9.3•10157
103 31 10 103 1.0•104 106 109
104 102 13 104 1.3•105 108 1012 Very big
105 3.2•102 17 105 1.7•106 1010 1015 computation
106 103 20 106 2.0•107 1012 1018
In the worst case, there is no matching of elements or the first matching element can found
at last on the list. In the best case, there is matching of elements at first on the list.
Worst-case efficiency
The worst-case efficiency of an algorithm is its efficiency for the worst case input of size n.
The algorithm runs the longest among all possible inputs of that size.
For the input of size n, the running time is Cworst(n) = n.
Best case efficiency
The best-case efficiency of an algorithm is its efficiency for the best case input of size n.
The algorithm runs the fastest among all possible inputs of that size n.
In sequential search, If we search a first element in list of size n. (i.e. first element equal to
a search key), then the running time is Cbest(n) = 1
Yet another type of efficiency is called amortized efficiency. It applies not to a single run of
an algorithm but rather to a sequence of operations performed on the same data structure.
Asymptotic notation is a notation, which is used to take meaningful statement about the
efficiency of a program.
The efficiency analysis framework concentrates on the order of growth of an algorithm’s
basic operation count as the principal indicator of the algorithm’s efficiency.
To compare and rank such orders of growth, computer scientists use three notations, they
are:
1.4.1 O - Big oh notation
1.4.2 Ω - Big omega notation
1.4.3 Θ - Big theta notation
Let t(n) and g(n) can be any nonnegative functions defined on the set of natural numbers.
The algorithm’s running time t(n) usually indicated by its basic operation count C(n), and g(n),
some simple function to compare with the count.
Example 1:
2
, where c2= 4 , c1= 2 and n0=2
Note: asymptotic notation can be thought of as "relational operators" for functions similar to the
corresponding relational operators for values.
= ⇒Θ(), ≤ ⇒ O(), ≥ ⇒ Ω(), < ⇒ o(), > ⇒ ω()
EXAMPLE 1: Compute the factorial function F(n) = n! for an arbitrary nonnegative integer n.
Since n!= 1•. . . . • (n − 1) • n = (n − 1)! • n, for n ≥ 1 and 0!= 1 by definition, we can compute
F(n) = F(n − 1) • n with the following recursive algorithm. (ND 2015)
ALGORITHM F(n)
//Computes n! recursively
//Input: A nonnegative integer n
//Output: The value of n!
if n = 0 return 1
else return F(n − 1) * n
Algorithm analysis
1.5.1 For simplicity, we consider n itself as an indicator of this algorithm’s input size.
i.e. 1.
1.5.2 The basic operation of the algorithm is multiplication, whose number of executions
we denote M(n). Since the function F(n) is computed according to the formula F(n)
= F(n −1)•n for n > 0.
1.5.3 The number of multiplications M(n) needed to compute it must satisfy the
equality
M(n) = M(n-1) + 1 for n > 0
To compute To multiply
F(n-1) F(n-1) by n
M(n − 1) multiplications are spent to compute F(n − 1), and one more multiplication is
needed to multiply the result by n.
Recurrence relations
The last equation defines the sequence M(n) that we need to find. This equation defines M(n)
not explicitly, i.e., as a function of n, but implicitly as a function of its value at another point, namely
n − 1. Such equations are called recurrence relations or recurrences.
Solve the recurrence relation (n) = (n − 1) + 1, i.e., to find an explicit formula for
M(n) in terms of n only.
To determine a solution uniquely, we need an initial condition that tells us the value with
which the sequence starts. We can obtain this value by inspecting the condition that makes the
algorithm stop its recursive calls:
if n = 0 return 1.
This tells us two things. First, since the calls stop when n = 0, the smallest value of n for
which this algorithm is executed and hence M(n) defined is 0. Second, by inspecting the pseudocode’s
exiting line, we can see that when n = 0, the algorithm performs no multiplications.
Thus, 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 for n = 0.
Algorithm analysis
The number of moves M(n) depends on n only, and we get the following recurrence
equation for it: M(n) = M(n − 1) + 1+ M(n − 1) for n > 1.
With the obvious initial condition M(1) = 1, we have the following recurrence relation for the
number of moves M(n):
M(n) = 2M(n − 1) + 1 for n > 1,
M(1) = 1.
We solve this recurrence by the same method of backward substitutions:
M(n) = 2M(n − 1) + 1 sub. M(n − 1) = 2M(n − 2) + 1
= 2[2M(n − 2) + 1]+ 1
= 22M(n − 2) + 2 + 1 sub. M(n − 2) = 2M(n − 3) + 1
= 2 [2M(n − 3) + 1]+ 2 + 1
2
…
= 2iM(n − i) + 2i−1 + 2i−2 + . . . + 2 + 1= 2iM(n − i) + 2i − 1.
…
Since the initial condition is specified for n = 1, which is achieved for i = n − 1,
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 time algorithm
EXAMPLE 3: An investigation of a recursive version of the algorithm which finds the number of
binary digits in the binary representation of a positive decimal integer.
ALGORITHM BinRec(n)
//Input: A positive decimal integer n
//Output: The number of binary digits in n’s binary representation
if n = 1 return 1
else return BinRec(⎝n/2])+ 1
Algorithm analysis
The number of additions made in computing BinRec(⎝n/2]) is A(⎝n/2]), plus one more
addition is made by the algorithm to increase the returned value by 1. This leads to the recurrence
A(n) = A(⎝n/2]) + 1 for n > 1.
then, the initial condition is A(1) = 0.
The standard approach to solving such a recurrence is to solve it only for n = 2k
A(2k) = A(2k−1) + 1 for k > 0,
A(20) = 0.
backward substitutions
A(2k) = A(2k−1) + 1 substitute A(2k−1) = A(2k−2) + 1
= [A(2k−2) + 1]+ 1= A(2k−2) + 2 substitute A(2k−2) = A(2k−3) + 1
= [A(2k−3) + 1]+ 2 = A(2k−3) + 3 ...
...
= A(2k−i) + i
...
= A(2k−k) + k.
Thus, we end up with A(2k) = A(1) + k = k, or, after returning to the original variable n = 2 k and
hence k = log2 n,
A(n) = log2 n ϵ Θ (log2 n).
EXAMPLE 1: Consider the problem of finding the value of the largest element in a list of n
numbers. Assume that the list is implemented as an array for simplicity.
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
Algorithm analysis
1.6.1 The measure of an input’s size here is the number of elements in the array, i.e.,
n.
1.6.2 There are two operations in the for loop’s body:
1.6.2.1 The comparison A[i]> maxval and
1.6.2.2 The assignment maxval←A[i].
1.6.3 The comparison operation is considered as the algorithm’s basic operation, because
the comparison is executed on each repetition of the loop and not the assignment.
1.6.4 The number of comparisons will be the same for all arrays of size n; therefore, there
is no need to distinguish among the worst, average, and best cases here.
1.6.5 Let C(n) denotes the number of times this comparison is executed. The algorithm
makes one comparison on each execution of the loop, which is repeated for each
value of the loop’s variable i within the bounds 1 and n − 1, inclusive. Therefore, the
sum for C(n) is calculated as follows:
−
() = ∑
=
i.e., Sum up 1 in repeated n-1 times
−
() = ∑ = − ∈ ()
=
EXAMPLE 2: Consider the element uniqueness problem: check whether all the Elements in a
given array of n elements are distinct.
ALGORITHM UniqueElements(A[0..n − 1])
//Determines whether all the elements in a given array are distinct
//Input: An array A[0..n − 1]
//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
Algorithm analysis
1.6.6 The natural measure of the input’s size here is again n (the number of elements
in the array).
1.6.7 Since the innermost loop contains a single operation (the comparison of two elements),
we
should consider it as the algorithm’s basic operation.
1.6.8 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.
1.6.9 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.
EXAMPLE 3: Consider matrix multiplication. Given two n × n matrices A and B, find the time
efficiency of the definition-based algorithm for computing their product C = AB. By definition, C
is an n × n matrix whose elements are computed as the scalar (dot) products of the rows of matrix A
and the columns of matrix B:
where C[i, j ]= A[i, 0]B[0, j]+ . . . + A[i, k]B[k, j]+ . . . + A[i, n − 1]B[n − 1, j] for every pair of
indices 0 ≤ i, j ≤ n − 1.
The total number of multiplications M(n) is expressed by the following triple sum:
Now, we can compute this sum by using formula (S1) and rule (R1)
.
The running time of the algorithm on a particular machine m, we can do it by the product