Professional Documents
Culture Documents
ALGORITHMS
AND
RECURSIONS
Data Structures Featuring C++ © Sridhar Radhakrishnan 1
Algorithms
• Algorithm consists of a set of finite steps satisfying the following conditions:
• Such programs called procedures fall under the general category of reactive systems.
Eg.: A computer operating system
• Resources generally attributed to a computer algorithm are space and running time.
• Running tim e and space required are ex pressed as a function of input size n.
• Let us use the notation A[i:n] to denote the set of array elements A[i],A[i+1],...,A[n].
• We first find the minimum integer in the array A[1:n] and swap it with the number in
A[1].
• Then we find the minimum in the array A[2:n] and swap it with the number in A[2] and
so on.
Algorithm Simplesort(A, n)
Input : An array A of n integers.
Output : A sorted array A in ascending order of the numbers it holds.
• Wall-clock time is inaccurate and not precise (measures only up to seconds precision)
• When a program is preempted, the measure clock will be include time taken by the
CPU to do other things.
• This estimate should closely reflect the experimental results to the extent possible.
• Primitive operations:
Assignm ent of a value to a variable.
Com paring tw o num bers.
Arithm etic operations such has addition, subtraction, m ultiplication etc.
betw een tw o num bers.
• The execution times of these operations depend on the architecture of the machine
that implements the algorithms.
Algorithm SUM(A,n)
Input : An array A storing n integers.
Output : Sum of the integers in the array, i.e., A[1]+A[2]+...+A[n].
1. sum←0 // 1 primitive operation
2. i ← 1 // 1 primitive operation
3. IF i>n THEN GO TO step 7 // 1*(n+1) + 1 primitive operations
4. sum ← sum+A[i] // 3*n primitive operations
5. i ← i+1 // 2*n primitive operations
6. GO TO step 3 // 1*n primitive operations
7. RETURN sum // 1 primitive operation
• Total number of operations = 7n+2+3.
• We say that the running time is proportional to 7n+5.
• The running time of the above algorithm is independent of the values
that are stored in the array A.
Algorithm FINDMIN(A,n)
Input : An array A storing n integers.
Output : The minimum value minA among all numbers in A.
1. minA←A[1] // 2 primitive operations
2. i ← 2 // 1 primitive operation
3. IF i>n THEN GO TO step 7 // 1*(n) + 1 primitive operations
4. IF A[i]< minA THEN minAA[i] // ??? primitive operations
At least 2*(n-1)
At most 2*(n-1) + 2*(n-1)
5. i ← i+1 // 2*(n-1) primitive operations
6. GO TO step 3 // 1*(n-1) primitive operations
7. RETURN minA // 1 primitive operation
i = n;
while (i >= 1) {
i ← i/2
}
5n2 + 8n + 4
• If the nature of the input is unknown,then worst case estimate is a better tool to
compare the performance of the algorithms than the best case estimate.
• We say that f(n) ∈ O(g(n)) (read as “f(n) is of order g(n)” or as “f(n) is big-oh of g(n)”)
if there is a real number c>0 and a fixed integer n0>=1 such that
f(n)<=cg(n) for every integer n>=n 0 .
f(n)=c 1 n+c 2 = O(n) .
• We should avoid saying that the running time is O(n 3 ) , although it is technically
correct to say it.
• The “big-Om ega ” notation captures the notion “greater than or equal to”.
• Formally, we say that a function f(n) ∈ Ω(g(n)) if there is a constant c>0 and an
integer n0>=1 such that f(n)>=cg(n), for all n>=n0.
• The lower bound defines the best possible efficiency of any algorithm that solves the
problem, including any algorithms that may be discovered in the future.
• The low er bound of the sorting algorithm is O(nlogn), where n is the number of
numbers to be sorted.
10 3.322 ns 10 ns 33 ns 100 ns 1 μs 1 μs
20 4.322 ns 20 ns 86 ns 400 ns 8 μs 1 ms
30 4.907 ns 30 ns 147 ns 900 ns 27 μs 1 sec
40 5.322 ns 40 ns 213 ns 2 μs 64 μs 18.3 min
10000000000
1000000000
100000000
10000000
1000000
Log n
Time Complexity
n
n Log n
100000
n^2
n^3
2^n
10000
1000
100
10
1
2 4 8 16 32 64 128 256 512 1,024
Input Size
• For recursion to be successful the recursive call must be on a problem smaller than the
original.
• Any recursive program consists of two parts: base case and recursive part.
ALGORITHM REC_FIBONACCI(N)
Input :The positive integer value N
Output:The Nth Fibonacci number
1. IF N = 0 THEN RETURN 0
2. IF N<=2 THEN RETURN 1
3. ELSE RETURN REC_FIBONACCI(N-1) + REC_FIBONACCI(N-2)
Fib(5)
return Fib(4) + Fib(3)
Fib(4) Fib(3)
return Fib(3) + Fib(2) return Fib(2) + Fib(1)
Fib(2) Fib(1)
return 1 return 1
• Example: Ex ponent(2,5) = 2 5 = 2 * 2 * 2 * 2 * 2 = 32 .
Exponent(N, x) = { 1
N*Exponent(N, x-1)
if x = 0.
if x > 0.
Algorithm EXPONENT(N,x )
Input : The positive integer value N and x.
Output:: The factorial of N
• Writing string S backward is equivalent to writing the last character and then writing
the string S decreased by the last character in reverse.
WriteBackward(S) =
{ do nothing if length of S is 0.
Output last character + WriteBackwards(S-1) if length of S > 0.
Algorithm WriteBackward(S)
Input : The string S with length S.length.
Output: The reverse of the string S.
• The S.length-1 is needed to specify the last index because the string is indexed
from zero to (length-1).
• The recursive calls placed on the stack and the corresponding output are as follows:
• At this stage the WriteBackwards (“”) statement is the base case and will not
display anything and the program terminates.
A B C A B C
This problem has a recursive solution which can be described in three steps.
1. Ignore the largest disk at the bottom and move the top N-1 disks from A to B (using C as a spare hole)
2. Move the largest disk from A to C.
3. Now move the N-1 disks from B to C using A as spare.
ALGORITHM SOLVETOWERS(count,source,destination,spare)
Input : The number of disks (count) and three pegs.
Output: Move all disks from the source peg to destination peg following
the rules and using the spare peg.
1. IF(count=1) THEN
2. Move a disk directly from source to destination.
3. ELSE
4. SolveTowers(count-1,source,destination ,spare)
5. SolveTowers(1,source,destination ,spare)
6. SolveTowers(count-1,source,destination ,spare)
7. ENDIF
• The most powerful piece in a chess game is the queen since it can attack any other
piece within its row, within its column, or along its diagonal.
• The eight queens problem is to figure out how to place eight queens on the chessboard
so that no queen can attack any other queen.
• No queen can reside in a row or column that contains another queen simplifies the
problem.
• Now the problem is only to check for possible attacks along the diagonals.
• The algorithm should place a queen in a column, provided that the queens have been
places correctly on the previous columns.
• Otherwise, once a queen is placed correctly on the current column the next column
needs to be considered.
• The problem starts with 8 columns and recursively breaks down to a problem with
fewer columns.
Here the algorithm needs to backtrack to the previous column and place the previous queen
differently.
Q
Q
Q
Q
#include<iostream>
using namespace std;
int factorial(int n);
{
int f;
if (n<1) return 1; Initially
f=factorial(n-1); Stack contents
// fact() return address
return n*f; Stack ptr
}
int main()
{
cout<<factorial(4)<<“ =4!”<<endl;
return 0;
}
#include<iostream>
using namespace std;
int factorial(int n);
Parameter
{
Stack contents
int f;
n 4
if (n<1) return 1;
f=factorial(n-1); stack ptr
#include<iostream>
using namespace std;
int factorial(int n);
{ Return address
int f; Stack contents
if (n<1) return 1; n 4
f=factorial(n-1); main() rtn adrs
// fact() return address stack ptr
return n*f;
}
int main()
{
cout<<factorial(4)<<“ =4!”<<endl;
reutrn 0;
}
#include<iostream>
using namespace std;
int factorial(int n);
{ Local variable
int f; Stack contents
if (n<1) return 1; n 4
f=factorial(n-1);
main() rtn adrs
// fact() return address
f ???
return n*f;
stack ptr
}
int main()
{
cout<<factorial(4)<<“ =4!”<<endl;
reutrn 0;
}
#include<iostream>
using namespace std;
int factorial(int n);
{ Parameter
int f; Stack contents
if (n<1) return 1; n 4
f=factorial(n-1);
main() rtn adrs
// fact() return address
f ???
return n*f;
n 3
}
stack ptr
int main()
{
cout<<factorial(4)<<“ =4!”<<endl;
reutrn 0;
}
#include<iostream>
using namespace std;
int factorial(int n);
{ Return address
int f; Stack contents
if (n<1) return 1; n 4
f=factorial(n-1);
main() rtn adrs
// fact() return address
f ???
return n*f;
n 3
}
fact() rtn adrs
int main()
{ stack ptr
cout<<factorial(4)<<“ =4!”<<endl;
reutrn 0;
}
#include<iostream>
using namespace std;
int factorial(int n);
{ Local variable
int f; Stack contents
if (n<1) return 1; n 4
f=factorial(n-1); main() rtn adrs
// fact() return address ???
return n*f; n 3
} fact() rtn adrs
int main()
f ???
{
stack ptr
cout<<factorial(4)<<“ =4!”<<endl;
reutrn 0;
}
#include<iostream>
using namespace std;
Parameter
int factorial(int n);
Stack contents
{
n 4
int f;
main() rtn adrs
if (n<1) return 1;
f ???
f=factorial(n-1);
n 3
// fact() return address
return n*f; fact() rtn adrs
} f ???
int main() n 2
{ stack ptr
cout<<factorial(4)<<“ =4!”<<endl;
reutrn 0;
}
#include<iostream>
using namespace std;
Return address
int factorial(int n); Stack contents
{
n 4
int f;
main() rtn adrs
if (n<1) return 1;
f ???
f=factorial(n-1);
n 3
// fact() return address
return n*f; fact() rtn adrs
} f ???
int main() n 2
{ fact() rtn adrs
cout<<factorial(4)<<“ =4!”<<endl; stack ptr
reutrn 0;
}
#include<iostream>
using namespace std;
Local variable
int factorial(int n); Stack contents
{
n 4
int f;
main() rtn adrs
if (n<1) return 1;
f ???
f=factorial(n-1);
n 3
// fact() return address
return n*f; fact() rtn adrs
} f ???
int main() n 2
{ fact() rtn adrs
cout<<factorial(4)<<“ =4!”<<endl; f ???
reutrn 0; stack ptr
}
reutrn 0;
}
#include<iostream>
using namespace std; Stack contents
int factorial(int n);
n 4
{
main() rtn adrs
int f;
f ???
if (n<1) return 1;
n 3
f=factorial(n-1);
fact() rtn adrs
// fact() return address
f ???
return n*f;
} n 2
#include<iostream>
using namespace std; Stack contents
int factorial(int n);
n 4
{
main() rtn adrs
int f;
f ???
if (n<1) return 1;
n 3
f=factorial(n-1);
fact() rtn adrs
// fact() return address
f ???
return n*f;
n 2
}
int main() fact() rtn adrs
{ f 1
cout<<factorial(4)<<“ =4!”<<endl; stack ptr 1
reutrn 0;
}
#include<iostream>
using namespace std; Stack contents
int factorial(int n);
n 4
{
main() rtn adrs
int f;
f ???
if (n<1) return 1;
n 3
f=factorial(n-1);
fact() rtn adrs
// fact() return address
f ???
return n*f;
} stack ptr 2
int main()
{
cout<<factorial(4)<<“ =4!”<<endl;
reutrn 0;
}
#include<iostream>
using namespace std; Stack contents
int factorial(int n);
n 4
{
main() rtn adrs
int f;
f ???
if (n<1) return 1;
n 3
f=factorial(n-1);
fact() rtn adrs
// fact() return address
f 2
return n*f;
} stack ptr 2
int main()
{
cout<<factorial(4)<<“ =4!”<<endl;
reutrn 0;
}
#include<iostream>
using namespace std; Stack contents
int factorial(int n);
n 4
{
main() rtn adrs
int f;
f ???
if (n<1) return 1;
stack ptr 3
f=factorial(n-1);
// fact() return address
return n*f;
}
int main()
{
cout<<factorial(4)<<“ =4!”<<endl;
reutrn 0;
}
#include<iostream>
using namespace std; Stack contents
int factorial(int n);
n 4
{
main() rtn adrs
int f;
f 6
if (n<1) return 1;
stack ptr 3
f=factorial(n-1);
// fact() return address
return n*f;
}
int main()
{
cout<<factorial(4)<<“ =4!”<<endl;
reutrn 0;
}
• In this recursive version of factorial(), every time a recursive function calls itself,
another activation record is created.
• If the recursion continues too long, activation records can in time overfill the stack
and cause the program to crash.
• This is the normal fate of programs whose recursive procedures have no base cases.
• In case of excessively large local variables, stack can be overfilled even in the
absence of recursion.This is typically found in the case of very large arrays.
• Divide-and-Conquer Algorithms:
Divide-and-conquer is a top-down technique for designing algorithms.
It first decomposes an instance of a problem into a number of smaller sub-
instances of the same problem, solving independently each of these sub-
instances and then merging the solutions to obtain the solution of the original
instance.
• Branch-and-Bound Algorithms:
Branch-and-Bound is sim ilar to the back tracking technique but differs in the
mechanism used to select and explore vertices in the graph.
A value is associated with exploring a vertex and if it this value is “promising” then
the vertex is explored.