Design & Analysis of Algorithms (DAA) Unit - I
Design & Analysis of Algorithms (DAA) Unit - I
Introduction
What is an algorithm?
• An algorithm is a finite set of instructions that, if followed, accomplishes a particular task
• All algorithms must satisfy the following criteria:
• Input. There are zero or more quantities that are externally supplied.
• Output. At least one quantity is produced.
• Definiteness. Each instruction is clear and unambiguous.
• Finiteness. If we trace out the instructions of an algorithm, then for all cases, the algorithm
terminates after a finite number of steps.
• Effectiveness. Every instruction must be basic enough to be carried out, in principle, by a
person using only pencil and paper.
Algorithm Specification
Algorithm can be described in three ways.
1. Natural language like English - we should ensure that each & every statement is definite.
2. Graphic representation called flowchart: - This method will work well when the algorithm is small&
simple.
3. Pseudo-code Method: - In this method, we should typically describe algorithms as program, which
resembles language like Pascal & algol.
Pseudocode Conventions:
1. Comments begin with // and continue until the end of line.
2. Blocks are indicated with matching braces {and}.
3. An identifier begins with a letter. The data types of variables are not explicitly declared. Compound
data types can be formed with records.
4. Assignment of values to variables is done using the assignment statement.
<Variable>:= <expression>;
5. There are two Boolean values TRUE and FALSE. Logical Operators used are AND, OR and NOT
Relational Operators <, <=,>,>=, =, != are used
6. Elements of multidimensional arrays are accessed using [ and ]
7. 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>
8. A conditional statement has the following forms.
If <condition> then <statement>
If <condition> then <statement-1> else <statement-1>
Case statement:
Case
{
: <condition-1> : <statement-1>
.
.
.
: <condition-n> : <statement-n>
: else : <statement-n+1>
}
9. Input and output are done using the instructions read & write.
10. There is only one type of procedure: Algorithm. It consists of
heading and body. Heading takes the form,
Algorithm Name (Parameter lists)
As an example, the following algorithm finds & returns the maximum of “n‟ given numbers:
1. algorithm Max(A,n)
2. // A is an array of size n
3. {
4. Result := A[1];
5. for I:= 2 to n do
6. if A[I] > Result then
7. Result :=A[I];
8. return Result;
9. }
Recursive Algorithms
• A Recursive function is a function that is defined in terms of itself.
• Similarly, an algorithm is said to be recursive if the same algorithm is invoked in the body.
• An algorithm that calls itself is Direct Recursive.
• Algorithm “A” is said to be Indirect Recursive if it calls another algorithm which in turns calls “A”.
• The Recursive mechanism, are externally powerful, but even more importantly, many times they can
express an otherwise complex process very clearly. Or these reasons we introduce recursion here.
An example Program using Recursion
int main(void)
{
fun(3);
return 0;
}
void fun(int a)
{
printf(" %d ",a);
if(a>0)
fun(a-1);
printf(" %d ",a);
}
int main(void)
{
fun(3);
return 0;
}
OUTPUT: 3 2 1 0 0 1 2 3
Performance Analysis
• Performance analysis: a priori estimate
• Space complexity: amount of memory the algorithm needs to run to completion
• Time complexity: amount of computer time the algorithm needs to run to completion
• Performance measurement: a posteriori testing
Space Complexity
• The space required by an algorithm is equal to the sum of the following two components −
• A fixed part that is a space required to store certain data and variables, that are independent of the
size of the problem. For example, simple variables and constants used, program size, etc.
• A variable part is a space required by variables, whose size depends on the size of the problem. For
example, dynamic memory allocation, recursion stack space, etc.
• Space complexity S(P) of any algorithm P is S(P) = C + SP(I), where C is the fixed part and S(I) is the
variable part of the algorithm, which depends on instance characteristic I.
Space Complexity:
Time Complexity:
• The time T(p) taken by a program P is the sum of the compile time and the run time (execution
time)
• The compile time does not depend on the instance characteristics. Also, we may assume that a
compiled program will be run several times without recompilation. This rum time is denoted by
tp(instance characteristics).
• The number of steps any problem statement is assigned depends on the kind of statement.
• For example, comments à 0 steps.
Assignment statements is 1 steps.
We introduce a variable, count into the program statement to increment count with initial value 0.
Statement to increment count by the appropriate amount are introduced into the program.
This is done so that each time a statement in the original program is executes count is incremented by
the step count of that statement.
Algorithm:
Algorithm sum(a, n) {
s= 0.0;
count = count+1;
for I=1 to n do{
count =count+1;
s=s + a[I];
count=count+1;
}
count=count+1;
count=count+1;
return s;
}
1. If the count is zero to start with, then it will be 2n+3 on termination. So each invocation of sum
execute a total of 2n+3 steps.
2. The second method to determine the step count of an algorithm is to build a table in which we list the
total number of steps contributes by each statement.
o First determine the number of steps per execution (s/e) of the statement and the
total number of times (i.e., frequency) each statement is executed.
o By combining these two quantities, the total contribution of all statements, the
step count for the entire algorithm is obtained.
Total 2n+3
Complexity of Algorithms
The complexity of an algorithm M is the function f(n) which gives the running time and/or storage
space requirement of the algorithm in terms of the size ‘n’ of the input data. Mostly, the storage space
required by an algorithm is simply a multiple of the data size ‘n’.
Complexity shall refer to the running time of the algorithm.
The function f(n), gives the running time of an algorithm, depends not only on the size ‘n’ of the input
data but also on the particular data. The complexity function f(n) for certain cases are:
1. Best Case: The minimum possible value of f(n) is called the best case.
2. Average Case: The expected value of f(n).
3. Worst Case: The maximum value of f(n) for any key possible input.
Asymptotic Notation
Formal way notation to speak about functions and classify them
The following notations are commonly use notations in performance analysis and used to characterize
the complexity of an algorithm:
1. Big–OH (O) ,
2. Big–OMEGA (Ω),
3. Big–THETA (Θ)
Big – Oh Notation:
• Big - Oh notation is used to define the upper bound of an algorithm in terms of Time Complexity.
• Big - Oh notation always indicates the maximum time required by an algorithm for all input
values. That means Big - Oh notation describes the worst-case of an algorithm time complexity.
• The worst-case complexity of the algorithm is the function defined by the maximum number of
steps taken on any instance of size n.
• The function f(n) = O(g(n)) iff there exist positive constants c and n0 such that f(n) ≤ c*g(n) for
all n, n≥n0.
Big – Omega Notation:
• Big-Omega notation is used to define the lower bound of an algorithm in terms of Time
Complexity.
• That means Big-Omega notation always indicates the minimum time required by an algorithm
for all input values. That means Big-Omega notation describes the best case of an algorithm time
complexity.
• The best-case complexity of the algorithm is the function defined by the minimum number of
steps taken on any instance of size n.
• The function f(n) = Ω(g(n)) iff there exist positive constants c and n0 such that f(n) ≥ c*g(n) for
all n, n≥n0.
• Big - Theta notation is used to define the average bound of an algorithm in terms of Time
Complexity.
• That means Big - Theta notation always indicates the average time required by an algorithm for
all input values. That means Big - Theta notation describes the average case of an algorithm time
complexity.
• The average-case complexity of the algorithm is the function defined by the average number of
steps taken on any instance of size n.
• The function f(n) = Ө(g(n)) iff there exist positive constants c1, c2 and n0 such that c1*g(n) ≤
f(n) ≤ c2*g(n) for all n, n≥n0.
(logn) is faster than O(n)
O(nlogn) is faster than O(n2) not as good as O(n)
O(n2) is faster than O(2n)
if n == 1 then
return (A[1], A[1])
else if n == 2 then
if A[1] < A[2] then
return (A[1], A[2])
else
return (A[2], A[1])
else
mid ← (low + high) / 2
[LMin, LMax] = DC_MAXMIN (A, low, mid)
[RMin, RMax] = DC_MAXMIN (A, mid + 1, high)
if LMax > RMax then
// Combine solution
max ← LMax
else
max ← RMax
end
if LMin < RMin then
// Combine solution
min ← LMin
else
min ← RMin
end
return (min, max)
end
Complexity analysis
The conventional algorithm takes 2(n – 1) comparisons in worst, best and average case.
DC_MAXMIN does two comparisons to determine the minimum and maximum element and creates two
problems of size n/2, so the recurrence can be formulated as
T(n) = 0, if n = 1
T(n) = 1, if n = 2
T(n) = 2T(n/2) + 2, if n > 2
Let us solve this equation using interactive approach.
T(n) = 2T(n/2) + 2 … (1)
By substituting n by (n / 2) in Equation (1)
T(n/2) = 2T(n/4) + 2
⇒ T(n) = 2(2T(n/4) + 2) + 2
= 4T(n/4) + 4 + 2 … (2)
By substituting n by n/4 in Equation (1),
T(n/4) = 2T(n/8) + 2
Substitute it in Equation (1),
T(n) = 4[2T(n/8) + 2] + 4 + 2
= 8T(n/8) + 8 + 4 + 2
= 23 T(n/23) + 23 + 22 + 21
.
.
After k – 1 iterations
It can be observed that divide and conquer approach does only [(3n/2) – 2] comparisons compared to
2(n – 1) comparisons of the conventional approach.
For any random pattern, this algorithm takes the same number of comparisons.
Merge Sort
The merge sort algorithm works from “the bottom up”
• start by solving the smallest pieces of the main problem
• keep combining their results into larger solutions
• eventually the original problem will be solved
8 3 2 9 7 1 54
8 3 2 9 7 1 5 4
8 3 2 9 71 5 4
8 3 2 9 7 1 5 4
3 8 2 9 1 7 4 5
2 3 8 9 1 4 5 7
1 2 3 4 5 7 8 9
Problem statement:
Given an array of elements, we want to arrange them in non-decreasing order.
Procedure:
Given a sequence of n elements a[1], a[2], ..……..a[n], the general idea is to imagine them split into two
sets a[1], a[2],…….a[(n/2)] and a[(n/2)+1],……a[n] each set is again individually sorted and the
resulting sorting sequences are merged to produce a single sorted sequence of n elements.
Quick Sort
Given an array of n elements (e.g., integers): If array only contains one element, return
Else
• pick one element to use as pivot.
• Partition elements into two sub-arrays:
• Elements less than or equal to pivot
• Elements greater than pivot
• Quicksort two sub-arrays
• Return results
Example:
Consider: arr[] = {10, 80, 30, 90, 40, 50, 70}
• Indexes: 0 1 2 3 4 5 6
• low = 0, high = 6, pivot = arr[h] = 70
• Initialize index of smaller element, i = -1
• Traverse elements from j = low to high-1
• j = 0: Since arr[j] <= pivot, do i++ and swap(arr[i], arr[j])
• i=0
• arr[] = {10, 80, 30, 90, 40, 50, 70} // No change as i and j are same
• j = 1: Since arr[j] > pivot, do nothing
• j = 5 : Since arr[j] <= pivot, do i++ and swap arr[i] with arr[j]
• i=3
• arr[] = {10, 30, 40, 50, 80, 90, 70} // 90 and 50 Swapped
When the order n of matrix reaches infinity, the utility of Strassen’s formula is shown by its asymptotic
superiority. For example, let us consider two matrices A and B of n*n dimension, where n is a power of
two. It can be observed that we can have four submatrices of order n/2 * n/2 from A, B, and their
product C where C is the resultant matrix of A and B.
Strassen’s Matrix multiplication can be performed only on square matrices where n is a power of 2.
Order of both of the matrices are n × n.
The procedure of Strassen’s matrix multiplication
Here is the procedure :
1. Divide a matrix of the order of 2*2 recursively until we get the matrix of order 2*2.
2. To carry out the multiplication of the 2*2 matrix, use the previous set of formulas.
3. Subtraction is also performed within these eight multiplications and four additions.
4. To find the final product or final matrix combine the result of two matrixes.
C00= d1 + d4 – d5 + d7
C01 = d3 + d5
C10 = d2 + d4
C11 = d1 + d3 – d2 – d6
Here, C00, C01, C10, and C11 are the elements of the 2*2 matrix.
C = d1+d4-d5+d7 d3+d5
d2+d4 d1+d3-d2-d6
end if
return (C)
end.
Implementation of Strassen’s matrix multiplication:
#include <stdio.h>
int main(){
int a[2][2],b[2][2],c[2][2],i,j;
int m1,m2,m3,m4,m5,m6,m7;
// Here we are scanning and printing the first matrix
printf("Enter the 4 elements of first matrix: ");
for(i=0;i<2;i++)
for(j=0;j<2;j++)
scanf("%d",&a[i][j]);
// Here we are scanning and printing the second matrix
printf("Enter the 4 elements of second matrix: ");
for(i=0;i<2;i++)
for(j=0;j<2;j++)
scanf("%d",&b[i][j]);
printf("\nThe first matrix is\n");
for(i=0;i<2;i++)
{
printf("\n");
for(j=0;j<2;j++)
printf("%d\t",a[i][j]);
}
printf("\nThe second matrix is\n");
for(i=0;i<2;i++){
printf("\n");
for(j=0;j<2;j++)
printf("%d\t",b[i][j]);
}
// Here we are applying the above mentioned formulae
m1= (a[0][0] + a[1][1])*(b[0][0]+b[1][1]);
m2= (a[1][0]+a[1][1])*b[0][0];
m3= a[0][0]*(b[0][1]-b[1][1]);
m4= a[1][1]*(b[1][0]-b[0][0]);
m5= (a[0][0]+a[0][1])*b[1][1];
m6= (a[1][0]-a[0][0])*(b[0][0]+b[0][1]);
m7= (a[0][1]-a[1][1])*(b[1][0]+b[1][1]);
c[0][0]=m1+m4-m5+m7;
c[0][1]=m3+m5;
c[1][0]=m2+m4;
c[1][1]=m1-m2+m3+m6;
// As we got the value of the elements, we now print them
printf("\n After performing multiplication \n");
for(i=0;i<2;i++){
printf("\n");
for(j=0;j<2;j++)
printf("%d\t",c[i][j]);
}
return 0;
}
O(nlog27) or O(n2.81)
***