DAA Module 1
DAA Module 1
Contents
1. Introduction
1.1. What is an Algorithm?
Algorithm design and analyysis process - We now briefly discuss a seq uence of steps one
typically goes through in desi gning and analyzing an algorithm
Understanding the Prob lem - From a practical perspective, the firstt thing you need to
do before designing an algorithm is to understand completely the problem given. An input
to an algorithm spec ifies an instance of the problem the algorithm solves. It is very
important to specify exactly the set of instances the algorithm needs to handle.
Choosing between Exac t and Approximate Problem Solving - The next principal
decision is to choose betwween solving the problem exactly and solving it approximately.
Because, there are important problems that simply cannot be solved exactly for most of
their instances and some of the available algorithms for solving a prob lem exactly can be
unacceptably slow because of the problem’s intrinsic complexity.
Designing an Algorithm and Data Structures - One should pay close attention to
choosing data structures appropriate for the operations performed by the algorithm. For
example, the sieve of Erat osthenes would run longer if we used a link ed list instead of
an array in its implementatio n. Algorithms + Data Structures = Progra ms
Methods of Specifying an Algorithm- Once you have designed an algorithm; you need to
specify it in some fa shion. These are the two options that are most widely used nowadays for
specifying algorithms. Using a natural language has an obvious appeal; however, the
inherent a mbiguity of any natural language makes a concise and clear description of
algorithms surprisingly difficult. Pseudocode is a mixture of a natural language and
programmin g language like constructs. Pseudocode is u sually more precise than natural
language, and its usage often yields more succinct algorithm descriptions.
Proving an Algorithm’s Correctness - Once an algorithm has been specified, you have
to prove its correctness. That is, you have to prove that the algorith m yields a required
result for every legitimate input in a finite amount of time. For some algorithms, a proof
of correctness is quite easy; for others, it can be quite complex. A com mon technique for
proving correctness is to use mathematical induction because an alg orithm’s iterations
provide a natural sequenc e of steps needed for such proofs.
Analyzing an Algorithm - After correctness, by far the most import ant is efficiency. In
fact, there are two kinds of algorithm efficiency: time efficiency, indicating how fast the
algorithm runs, and space efficiency, indicating how much extra mem ory it uses.
Another desirable characteristic o f an algorithm is simplicity. Unlike efficiency, which
can be precisely defined and inve stigated with mathematical rigor, simplicity, like
beauty, is to a considerable degree in the eye of the beholder.
Using the combination of simple English and C++, the algorithm fo r selection sort is
specified as follows.
Recursive algorithms
An algorithm is said to be recursive if the same algorithm is invoked in the body (direct
recursive). Algorithm A is said to be indirect recursive if it calls another algorithm which in
turn calls A.
Example 1: Factorial computa tion n! = n * (n-1)!
Example 2: Binomial coefficient computation
General framework for analy zing the efficiency of algorithms is discuss ed here. There are
two kinds of efficiency: time efficiency and space efficiency. Time efficiency indicates how
fast an algorithm in question runs; space efficiency deals with the extra s pace the algorithm
requires.
In the early days of electronic computing, both resources time and space were at a premium.
Now the amount of extra spa ce required by an algorithm is typically not o f as much
concern, In addition, the research experience has shown that for most problems, wee can
achieve much more spectacular progress in speed than in space. Therefore, following a well-
established tradition of algorithm textbooks, we primarily concentrate on time efficiency.
It is observed that almost all algorithms run longer on larger inputs. Fo r example, it takes
longer to sort larger arrays, multiply larger matrices, and so on. Therefore, it is logical to
investigate an algorithm's e fficiency as a function of some parameter n indicating the
algorithm's input size.
There are situations, where the choice of a parameter indicating an input size does matter.
The choice of an appropriate size metric can be influenced by operations of the algorithm in
question. For example, how should we measure an input's size fo r a spell-checking
algorithm? If the algorithm examines individual characters of its inpu t, then we should
measure the size by the number of characters; if it works by processing words, we should
count their number in the inpu t.
We should make a special no te about measuring the size of inputs for algorithms involving
properties of numbers (e.g ., checking whether a given integer n is prime). For such
algorithms, computer scientistts prefer measuring size by the number b of b its in the n's
binary representation: log n
1. This metric usually gives a better idea about the efficiency of algorithms in question.
To measure an algorithm's effficiency, we would like to have a metric tha t does not
depend on these extraneous factors. One possible approach is to count the numb er of times
each of the algorithm's operations is executed. This approach is both excessively difficult
and, as we shall see, usually unnecessary . The thing to do is to identify the most imp ortant
operation of the algorithm, called the bas ic operation, the operation contributing the most to
the total running time, and compute th e number of times the basic operation is executed.
For example, most sorting algorithms work by comparing elements (k eys) of a list being
sorted with each other; for succh algorithms, the basic operation is a key co mparison.
As another example, algorithms for matrix multiplication and poly nomial evaluation
require two arithmetic operations: multiplication and addition.
Let cop be the execution time of an algorithm's basic operation on a partic ular computer, and
let C(n) be the number of tim es this operation needs to be executed for t his algorithm. Then
we can estimate the running time T(n) of a program implementing this algorithm on that
computer by the formula:
unless n is extremely large or very small, the formula can give a reasona ble estimate of the
algorithm's running time.
It is for these reasons that the efficiency analysis framework ignores multiplicative constants
and concentrates on the count's order of growth to within a constant multiple for large-size
inputs.
Orders of Growth
Why this emphasis on the co unt's order of growth for large input sizes? Because for large
values of n, it is the function's order of growth that counts: just look at ta ble which contains
values of a few functions particularly important for analysis of algorithms.
Table: Values
of several
functions
important for
analysis of
algorithms
Algorithms that require an e xponential number of operations are practical for solving only
problems of very small sizes.
Definition: The worst-case efficiency of an algorithm is its efficiency for the worst-case
input of size n, for which the algorithm runs the longest among all possible inputs of that size.
Consider the algorithm for se quential search.
The running time of above algorithm can be quite different for the sam e 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: Cworst(n) = n.
In general, we analyze the alggorithm to see what kind of inputs yield the largest value of the
basic operation's count C(n) a mong all possible inputs of size n and then compute this worst-
case value Cworst (n). The worst-case analysis provides algorithm's efficien cy by bounding
its running time from above. Th us 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.
Definition: The best-case eff iciency of an algorithm is its efficiency for the best-case input
of size n, for which the algoritthm runs the fastest among all possible inputss of that size.
We determine the kind of inputs for which the count C(n) will be the smallest among all
possible inputs of size n. For example, for sequential search, best-case inp uts are lists of size
n with their first elements equ al to a search key; Cbest(n) = 1.
The analysis of the best-case efficiency is not nearly as important as that of the worst-case
efficiency. Also, neither the worst-case analysis nor its best-case counterpart yields the
necessary information about an algorithm's behavior on a "typical" or "r andom" input. This
information is provided by average-case efficiency.
Definition: the average-case complexity of an algorithm is the amount of time used by the
algorithm, averaged over all p ossible inputs.
Let us consider again sequential search. The standard assumptions are tha t (a) the probability
of a successful search is equ al top (0 ≤ p ≤ 1) and (b) the probability of the first match
occurring in the ith position of the list is the same for every i. We can find the average
number of key comparisons Cavg (n) as follows.
In the case of a successful search, the probability of the first match occurring in the i th
position of the list is p/n for every i, and the number of comparisons madde by the algorithm
in such a situation is obvio usly i. In the case of an unsuccessful search, the number of
comparisons is n with the pro bability of such a search being (1- p). Theref ore,
Investigation of the average-c ase efficiency is considerably more difficult than investigation
of the worst-case and best-case efficiencies. But there are many important algorithms for
which the average case effi ciency is much better than the overly pes simistic worst-case
efficiency would lead us to believe. Note that average-case efficiency can not be obtained by
taking the average of the wors t-case and the best-case efficiencies.
2. Performance Analysis
Total amount of computer m emory required by an algorithm to compl ete its execution is
called as space complexity of that algorithm. The Space required by an a lgorithm is the sum
of following components
A fixed part that is in dependent of the input and output. This incl udes memory space
for codes, variables, c onstants and so on.
A variable part that depends on the input, output and recursion sta ck. ( We call these
parameters as instance characteristics)
Here fixed component depend s on the size of a, b and c. Also instance charracteristics
Sp=0 Example-2: Let us consider t he algorithm to find sum of array.
For the algorithm given here the problem instances are characterized b y n, the number of
elements to be summed. The space needed by a[ ] depends on n. So the space complexity can
be written as; Ssum(n) ≥ (n+3) n for a[ ], One each for n, i and s.
Usually, the execution time or run-time of the program is refereed as its time complexity
denoted by tp (instance char acteristics). This is the sum of the time taaken to execute all
instructions in the program.
We can determine the steps needed by a program to solve a particular problem instance in
two ways.
In the first method we introd uce a new variable count to the program wh ich is initialized to
zero. We also introduce stat ements to increment count by an appropriate amount into the
program. So when each time original program executes, the count also incremented by the
step count.
Example-1: Consider the algorithm sum( ). After the introduction of the count the program
will be as follows.
From the above we can esti mate that invocation of sum( ) executes tot al number of 2n+3
steps.
The second method to deter mine the step count of an algorithm is to bui ld a table in which
we list the total number of steps contributed by each statement. An example is shown below.
The above thod is both exces sively difficult and, usually unnecessary. T he thing to do is to
identify the most important operation of the algorithm, called the basic operation, the
operation contributing the moost to the total running time, and compute thhe number of times
the basic operation is execute d.
Trade-off
There is often a time-space-tradeoff involved in a problem, that is, it can not be solved with
few computing time and low memory consumption. One has to make a compromise and to
exchange computing time foor memory consumption or vice versa, depending on which
algorithm one chooses and how one parameterizes it.
3. Asymptotic Notation s
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 efficienc y. To compare
and rank such orders of growth,, computer scientists use three notations: O(big oh), Ω(big
omega), Θ (big theta) and o(little oh)
Informally, O(g(n)) is the set of all functions with a lower or same order of growth as g(n)
Examples:
Strategies for Big-O Somet imes the easiest way to prove that f(n) = O( g(n)) is to take c to
be the sum of the positive coefficients of f(n). We can usually ig nore the negative
coefficients.
n ≥ n for all n ≥ 0,
i.e., we can select c = 1 and n0 = 0.
Example:
Example: n2 + 5n + 7 = Θ(n2)
The following theorem shows us that proving f(n) = Θ(g(n)) is nothing new:
Theorem: f(n) = Θ(g(n)) if and only if f(n) = O(g(n)) and f(n) = Ω(g(n)).
Thus, we just apply the previous two strategies.
3.4. Little Oh The function f(n) = o(g(n)) [ i.e f of n is a little oh of g of n ] if and only if
lim 0
→
Example:
Note that number of comparissions will be same for all arrays of size n. Th erefore, no need
to distinguish worst, best and average cases.
Total number of basic operati ons (comparison) are,
Example-2: To check whether all the elements in the given array are distinct
Algorithm
Here basic operation is comparison. The maximum no. of comparisons h appen in the worst
case. (i.e. all the elements in the array are distinct and algorithms return tru e).
Total number of basic operati ons (comparison) in the worst case are,
Other than the worst case, the total comparisons are less than . ( For example if the first
two elements of the array are equal, only one comparison is computed). So in general C(n)
=O(n2)
Example-3: To perform mattrix multiplication
Algorithm
Suppose if we take into account of addition; Algoritham also have same nu mber of
additions A(n) = n3
Total running time:
Example-1
Algorithm
The number of multiplication s M(n) needed to compute it must satisfy the equality
Condition that makes the alg orithm stop if n = 0 return 1. Thus recurrence relation and
initial condition for the algori thm’s number of multiplications M(n) can be stated as
….
Example-2: Tower of Hanoi puzzle. In this puzzle, There are 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 re cursive solution, which is illustrated in Figur e.
To move n>1 disks from p eg 1 to peg 3 (with peg 2 as auxiliary),
O we first move recursively n-1 disks from peg 1 to peg 2 (with peg 3 as auxiliary),
O then move the largest disk directly from peg 1 to peg 3, and,
O finally, move recu rsively n-1 disks from peg 2 to peg 3 (using p eg 1 as auxiliary).
If n = 1, we move the single disk directly from the source peg to the destination peg.
We have the following recurr ence relation for the number of moves M(n):
The pattern of the first three sums on the left suggests that the next one will be
24 M(n − 4) + 23 + 22 + 2 + 1, and generally, after i substitutions, we get
Since the initial condition is specified for n = 1, which is achieved for i = n - 1, we get the
following formula for the solu tion to recurrence,
Alternatively, by counting thhe number of nodes in the tree obtained by recursive calls, we
can get the total number of calls made by the Tower of Hanoi algorithm:
Figure: Tree of recursive c alls made by the recursive algorithm for the Tower of Hanoi
puzzle.
Example-3
The standard approach to solving such a recurrence is to solve it only f or n = 2k and then
take advantage of the theore m called the smoothness rule which clai ms that under very
broad assumptions the order of growth observed for n = 2k gives a correcct answer about the
order of growth for all values of n.
The sorting problem is to rearrange the items of a given list in non-decreasing order. As a
practical matter, we usually need to sort lists of numbers, characters from an alphabet or
character strings.
Although some algorithms are indeed better than others, there is no algorithm that would be
the best solution in all situatio ns. Some of the algorithms are simple but rellatively slow,
while others are faster but more c omplex; some work better on randomly orddered inputs,
while others do better on almost-soorted lists; some are suitable only for lists residing in the
fast memory, while others can be adapted for sorting large files stored on a dis k; and so on.
Two properties of sorting alg orithms deserve special mention. A sorting algorithm is called
stable if it preserves the relative order of any two equal elements in its input. The second
notable feature of a sorting algorithm is the amount of extra memory the algorithm requires.
An algorithm is said to be in- place if it does not require extra memory, ex cept, possibly, for
a few memory units.
4.2. Searching
The searching problem deals with finding a given value, called a search key, in a given set.
(or a multiset, which permits several elements to have the same value). There are plenty of
searching algorithms to choos e from. They range from the straightforward sequential search
to a spectacularly efficient buut limited binary search and algorithms bas ed on representing
the underlying set in a differe nt form more conducive to searching. The l atter algorithms are
of particular importance for re al-world applications because they are indis pensable for
storing and retrieving information from large databases.
4.3. String Processing
In recent decades, the rapid proliferation of applications dealing with non-numerical data has
intensified the interest of researchers and computing practitioners in string-handling
algorithms. A string is a sequence of characters from an alphabet. String-processing
algorithms have been impo rtant for computer science in conjuncti on with computer
languages and compiling issu es.
4.4. Graph Problems
One of the oldest and most in teresting areas in algorithmics is graph algorithms. Informally, a
graph can be thought of as a collection of points called vertices, some of w hich are connected by
line segments called ed ges. Graphs can be used for modeling a wide variety of applications,
including transp ortation, communication, social and economic networks, project scheduling, and
games. Stud ying different technical and social aspects of the Internet in
particular is one of the active areas of current research involving computer scientists,
economists, and social scientists.
4.5. Combinatorial Problems
Generally speaking, combinatorial problems are the most difficult problems in computing,
from both a theoretical and practical standpoint. Their difficulty stems from the following
facts. First, the number of combinatorial objects typically grows extremely fast with a
problem’s size, reaching u nimaginable magnitudes even for moderate-sized instances.
Second, there are no know n algorithms for solving most such problems exactly in an
acceptable amount of time.
A (one-dimensional) array is a sequence of n items of the same data type that are stored
contiguously in computer memory and made accessible by specifying a value of the array’s
index.
A linked list is a sequence of zero or more elements called nodes, each coontaining two kinds of
information: some data an d one or more links called pointers to other nodes of the linked list. In
a singly linked list, each node except the last one contains a single pointer to the next element.
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 an d its predecessor.
A list is a finite sequence of data items, i.e., a collection of data items ar ranged in a certain
linear order. The basic operations performed on this data structure are searching for,
inserting, and deleting an e lement. Two special types of lists, stacks and queues, are
particularly important.
A stack is a list in which ins ertions and deletions can be done only at t he end. This end is
called the top because a stack is usually visualized not horizontally but v ertically—akin to a
stack of plates whose “operations” it mimics very closely.
A queue, on the other hand, is a list from which elements are deleted fr om one end of the
structure, called the front (thi s operation is called dequeue), and new elements are added to
the other end, called the re ar (this operation is called enqueue). Con sequently, a queue
operates in a “first-in–first-o ut” (FIFO) fashion—akin to a queue of cus tomers served by a
single teller in a bank. Que ues also have many important applications, including several
algorithms for graph problem s.
Many important applications require selection of an item of the highest priority among a
dynamically changing set of candidates. A data structure that seeks to satisfy the needs of
such applications is called a priority queue. A priority queue is a collection of data items
from a totally ordered uni verse (most often, integer or real numbe rs). The principal
operations on a priority queue are finding its largest element, deleting its largest element, and
adding a new element.
5.2. Graphs
Graph Representations - Gr aphs for computer algorithms are usually represented in one of
two ways: the adjacency matrrix and adjacency lists.
The adjacency matrix of a graph with n vertices is an n x n boolean m atrix with one row and
one column for each of th e graph’s vertices, in which the element in th e ith row and the jth
VTU IN POCKETS Page|1.25
Lecture Notes | – Design & Analysis of Algorithms | Module 1: Introduction
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.
The adjacency lists of a graph or a digraph is a collection of linked lists, one for each vertex,
that contain all the vertices ad jacent to the list’s vertex (i.e., all the vertices connected to it by
an edge).
Weighted Graphs: A weigh ted graph (or weighted digraph) is a graph (or digraph) with
numbers assigned to its edges. These numbers are called weights or costs.
Among the many properties o f graphs, two are important for a great number of applications:
connectivity and acyclicity. B oth are based on the notion of a path. A path from vertex u to
vertex v of a graph G can be defined as a sequence of adjacent (conn ected by an edge)
vertices that starts with u and ends with v.
A graph is said to be connected if for every pair of its vertices u and v th ere is a path from u
to v. Graphs with several co nnected components do happen in real-worl d applications. It is
important to know for many applications whether or not a graph unde r consideration has
cycles. A cycle is a path of a positive length that starts and ends at the sa me vertex and does
not traverse the same edge mo re than once.
5.3. Trees
A tree (more accurately, a fre e tree) is a connected acyclic graph. A grap h that has no cycles
but is not necessarily connected is called a forest: each of its connected co mponents is a tree.
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 v ertices to the other.
This property makes it possib le to select an arbitrary vertex in a free treee and consider it as
the root of the so-called root ed 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 from the root still below (level 2), and so on.
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 longes t simple path from the root to a leaf.
Ordered Trees- An ordered tree is a rooted tree in which all the children of each vertex are
ordered. It is convenient to as sume that in a tree’s diagram, all the childre n are ordered left
to right. A binary tree can be deefined as an ordered tree in which every verte x has no more
than two children and each child i s designated as either a left child or a right child of its
parent; a binary tree may also be empty .
If a number assigned to each parental vertex is larger than all the numberrs in its left subtree
and smaller than all the num bers in its right subtree. Such trees are cal led binary search
trees. Binary trees and binary search
trees have a wide vari ety of
applications in computer sciennce.
A set can be described as an unordered collection (possibly empty) of distinct items called
elements of the set. A specific set is defined either by an explicit listing of its elements (e.g.,
S = {2, 3, 5, 7}) or by speci fying a property that all the set’s elements and only they must
satisfy (e.g., S = {n: n is a pri me number smaller than 10}).
The most important set operaations are: checking membership of a given item in a given set;
finding the union of two sets, which comprises all the elements in either or both of them; and
finding the intersection of two sets, which comprises all the common elem ents in the sets.
Sets can be implemented in computer applications in two ways. The first considers only sets
that are subsets of some large set U, called the universal set. If set U has n elements, then any
subset S of U can be represen ted by a bit string of size n, called a bit vecttor, in which the ith
element is 1 if and only if the ith element of U is included in set S.
The second and more common way to represent a set for computing purposes is to use the list
structure to indicate the set’s elements. This is feasible only for finite setss. The requirement
for uniqueness is sometimes circumvented by the introduction of a m ultiset, or bag, an
unordered collection of items that are not necessarily distinct. Note that if a set is represented
by a list, depending on the application at hand, it might be worth maint aining the list in a
sorted order.
*****