ADA Module 1
ADA Module 1
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:
Algorithm Specification
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
Stop
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.
6 .The following looping statements are employed. For, while and repeat-until
While Loop:
While < condition > do
{ <statement-1> . . . <statement-n> }
For Loop:
For variable: = value-1 to value-2 step step do
{ <statement-1> . . . <statement-n> }
repeat-until:
repeat <statement-1> . . . <statement-n> until<condition>
Case
statement:
Case
{
: <condition-1> : <statement-1>
...
: <condition-n> : <statement-n>
: else : <statement-n+1>
}
8.Input and output are done using the instructions read & write.
Algorithm Max(A,n)
//A is an array of size n
{
Result := A[1];
for i:= 2 to n do
if A[i] > Result then
Result :=A[i];
return Result;
}
In this algorithm (named Max), A & n are procedure parameters. Result & i are Localvariables.
FUNDAMENTALS OF THE ANALYSIS OF ALGORITHM EFFICIENCY
The efficiency of an algorithm can be in terms of time and space. The algorithm efficiency
can be analyzed by the following ways.
a. Analysis Framework.
b. Asymptotic Notations and its properties.
c. Mathematical analysis for Recursive algorithms.
d. Mathematical analysis for Non-recursive algorithms.
Analysis Framework
There are two kinds of efficiencies to analyze the efficiency of any algorithm. They are:
• Time efficiency, indicating how fast the algorithm runs, and
• Space efficiency, indicating how much extra memory it uses.
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
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:
• O - Big oh notation
• Ω - Big omega notation
• Θ - Big theta notation
O - Big oh notation
A function t(n) is said to be in O(g(n)), denoted 𝑡 (𝑛) ∈ 𝑂(𝑔(𝑛)), 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
𝑡 (𝑛) ≤ 𝑐𝑔(𝑛) fo𝑟 𝑎𝑙𝑙 𝑛 ≥ 𝑛0.
Where t(n) and g(n) are nonnegative functions defined on the set of natural numbers.
O = Asymptotic upper bound = Useful for worst case analysis = Loose bound
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.
Where t(n) and g(n) are nonnegative functions defined on the set of natural numbers.
Θ = Asymptotic tight bound = Useful for average case analysis
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
definition O being 2c3 = 2 max{c1, c2} and max{n1, n2}, respectively.
The property implies that the algorithm’s overall efficiency will be determined by the part
with a higher order of growth, i.e., its least efficient part.
t1(n) ∈ O(g1(n)) and t2(n) ∈ O(g2(n)), then t1(n) + t2(n) ∈ O(max{g1(n), g2(n)}).
Summation formulas
MATHEMATICAL ANALYSIS FOR RECURSIVE ALGORITHMS
General Plan for Analyzing the Time Efficiency of Recursive Algorithms
1. Decide on a parameter (or parameters) indicating an input’s size.
2. Identify the algorithm’s basic operation.
3. Check whether the number of times the basic operation is executed can vary on different
inputs of the same size; if it can, the worst-case, average-case, and best-case efficiencies must
be investigated separately.
4. Set up a recurrence relation, with an appropriate initial condition, for the number of times
the basic operation is executed.
5. Solve the recurrence or, at least, ascertain the order of growth of its solution.
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
• For simplicity, we consider n itself as an indicator of this algorithm’s input size. i.e. 1.
• 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)•nfor n >
0.
• 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 𝑀(𝑛) = 𝑀(𝑛 − 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.
Since the recursive calls end when n is equal to 1 and there are no additions made
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 = 2k 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
• The measure of an input’s size here is the number of elements in the array, i.e., n.
• There are two operations in the for loop’s body:
o The comparison A[i]> maxval and
o The assignment maxval←A[i].
• 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.
• 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.
• 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:
𝑛−1
𝑐(𝑛) = ∑ 1
i=1
i.e., Sum up 1 in repeated n-1 times
𝑛− 1
𝑐(𝑛) = ∑ 1 = 𝑛 − 1 ϵ Θ(n)
i=1
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
• The natural measure of the input’s size here is again n (the number of elements in the array).
• Since the innermost loop contains a single operation (the comparison of two elements), we
should consider it as the algorithm’s basic operation.
• 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.
• 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)
.
EXAMPLE 4 The following algorithm finds the number of binary digits in the binary
representation of a positive decimal integer.
ALGORITHM Binary(n)
//Input: A positive decimal integer n
//Output: The number of binary digits in n’s binary
count←1
while n > 1 do
count←count + 1
n←[n/2]
return count
Algorithm
analysis
• An input’s size is n.
• The loop variable takes on only a few values between its lower and upper limits.
• Since the value of n is about halved on each repetition of the loop, the answer should
beabout log2 n.
• The exact formula for the number of times.
• The comparison 𝑛 > 1 will be executed is actually ہlog2 n] + 1.
(i) Sorting
• The sorting problem is to rearrange the items of a given list in nondecreasing
(ascending)order.
• Sorting can be done on numbers, characters, strings or records.
• To sort student records in alphabetical order of names or by student number or by
studentgrade-point average. Such a specially chosen piece of information is called a key.
• An algorithm is said to be in-place if it does not require extra memory, E.g., Quick sort.
• A sorting algorithm is called stable if it preserves the relative order of any two
equalelements in its input.
(ii) Searching
• The searching problem deals with finding a given value, called a search key, in a given
set.
• E.g., Ordinary Linear search and fast binary search.
• Another extension is the structure called the doubly linked list, in which every node,except
the first and the last, contains pointers to both its successor and its predecessor.
7.2 Graphs
• A graph is informally thought of as a collection of points in the plane called
―vertices‖ or
nodes,‖ some of them connected by line segments called ―edges‖ or ―arcs.‖
• A graph G is called undirected if every edge in it is undirected. A graph whose every
edge
is directed is called directed. Directed graphs are also called digraphs.
• The graph depicted in Figure (a) has six vertices and seven undirected
edges: V = {a, b, c, d, e, f }, E = {(a, c), (a, d), (b, c), (b, f ), (c, e), (d, e),
(e,f )}. The digraph depicted in Figure 1.6b has six vertices and eight
directed edges:
V = {a, b, c, d, e, f }, E = {(a, c), (b, c), (b, f ), (c, e), (d, a), (d, e), (e, c), (e, f )}.
The adjacency matrix of a graph with n vertices is an n X n Boolean matrix with one row and
one column for each of the graphs vertices, in which the element in the ith row and the jth
column 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.
Figure : (a) Adjacency matrix and (b) adjacency lists of the graph in Figure (a)
Figure : (a) Adjacency matrix and (b) adjacency lists of the graph in Figure (a)
Figure : (a) Adjacency matrix and (b) adjacency lists of the graph in Figure (a)
Weighted Graphs
A weighted graph (or weighted digraph) is a graph (or digraph) with numbers
assigned to its edges. These numbers are called weights or costs.
This approach is illustrated in below figure (b) for the weighted graph in
figure (a).
(For some applications, it is more convenient to put 0‘s on the main
diagonal of the adjacency matrix.)
Adjacency lists for a weighted graph have to include in their nodes not only
the name of an adjacent vertex but also the weight of the corresponding edge
(Figure c).
figure : (a) Weighted graph. (b) Its weight matrix. (c) Its adjacency lists.
7.3 Trees
A tree (more accurately, a free tree) is a connected acyclic graph as in figure (a)below.
A graph that has no cycles but is not necessarily connected is called a forest: eachof its
connected components is a tree as in figure (b) below
Trees have several important properties other graphs do not have. In particular, the
number
of edges in a tree is always one less than the number of its vertices: |E| = |V| − 1.
Rooted Trees
Another very important property of trees is the fact that for every two vertices in a
tree, there always exists exactly one simple path from one of these vertices to the
other.
This property makes it possible to select an arbitrary vertex in a free tree andconsider
it as the root of the so-called rooted tree.
A rooted tree is usually depicted by placing its root on the top (level 0 of the tree), the
vertices adjacent to the root below it (level 1), the vertices two edges apart fromthe
root still
below (level 2), and so on. Figure below presents such a transformation from a free
tree to a \rooted tree.
Figure : (a) Free tree. (b) Its transformation into a rooted tree.
For any vertex v in a tree T , all the vertices on the simple path from the root to that
vertex are called ancestors of v.
The vertex itself is usually considered its own ancestor; the set of ancestors thatexcludes
the vertex itself is referred to as the set of proper ancestors.
If (u, v) is the last edge of the simple path from the root to vertex v (and u ≠ v), u is
said to be the parent of v and v is called a child of u; vertices that have the same
parent are said to be siblings.
A vertex with no children is called a leaf ; a vertex with at least one child is called
parental.
All the vertices for which a vertex v is an ancestor are said to be descendants of v;the
proper descendants exclude the vertexvitself.
All the descendants of a vertex v with all the edges connecting them form the
subtree of T rooted at that vertex.
Thus, for the tree in Figure (b) above, the root of the tree is a; vertices d, g, f, h, and I
are leaves, and vertices a, b, e, and c are parental; the parent of b is a; the children of
b are c and g; the siblings of b are d and e; and the vertices of the subtree rooted at
✓
b are {b, c, g, h, i}.
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.
For example, the depth of vertex c of the tree in Figure (b) above is 2, and the heightof
the tree is 3.
Thus, if we count tree levels top down starting with 0 for the root‘s level, the depth
of a vertex is simply its level in the tree, and the tree‘s height is the maximum level
of its vertices
Ordered Trees
An ordered tree is a rooted tree in which all the children of each vertex are ordered.
✓
It is convenient to assume that in a tree‘s diagram, all the children are ordered left to right.
A binary tree can be defined as an ordered tree in which every vertex has no more
than two children and each child is designated as either a left child or a right child ofits
parent; a binary tree may also be empty.
The binary tree with its root at the left (right) child of a vertex in a binary tree is
called the left (right)subtreeof that vertex. Since left and right subtrees are binary trees
as well, abinary tree can also be defined recursively. This makes it possible to solve
many problems involving binary trees by recursive algorithms.
In Figure (b), some numbers are assigned to vertices of the binary tree in Figure (a)
above.
Note that a number assigned to each parental vertex is larger than all the numbersin its
left subtree and smaller than all the numbers in its right subtree. Such treesare called
binarysearch trees.
A binary tree is usually implemented for computing purposes by a collection of
nodes corresponding to vertices of the tree. Each node contains some information
associated with the vertex (its name or some value assigned to it) and two pointers to
the nodes representing the left child and right child of the vertex, respectively.
Figure below illustrates such an implementation for the binary search tree in Figure
(b) above.
Figure : Standard implementation of the binary search tree in Figure (b) above
We can avoid this inconvenience by using nodes with just two pointers, as we did
for binary trees. Here, however, the left pointer will point to the first child of the
vertex, and the right pointer will point to its next sibling. Accordingly, this
representation is called the firstchild–next sibling representation.
Thus, all the siblings of a vertex are linked via the nodes‘ right pointers in a singly
Linked list, with the first element of the list pointed to by the left pointer of their
parent.
Figure (a) below illustrates this representation for the tree in Figure (b) above It is
not difficult to see that this representation effectively transforms an ordered treeinto
a binary tree said to be associated with the ordered tree.
Figure : (a) First child–next sibling representation of the tree in Figure 1.11b. (b) Its binary
tree representation.