Faculty of Computer and Artificial Intelligence
Sadat University
Analysis and Design of Algorithms (CS 302)
Lecture :2
“Introduction (Cont..)+ Recursion ”
Dr.Sara A Shehab
Analysis of
Algorithms
Input Algorithm Output
An algorithm is a step-by-step procedure for solving a
problem in a finite amount of time.
© 2004 Goodrich, Tamassia
Running Time
▪ Most algorithms transform input best case
average case
objects into output objects. worst case
120
▪ The running time of an 100
algorithm typically grows with
Running Time
80
the input size. 60
▪ Average case time is often 40
difficult to determine. 20
0
▪ We focus on the worst case 1000 2000 3000 4000
Input Size
running time.
◦ Easier to analyze
◦ Crucial to applications such as games,
finance and robotics
ANALYSIS OF ALGORITHMS 3
© 2004 Goodrich, Tamassia
Experimental Studies
▪ Write a program implementing 9000
the algorithm 8000
7000
▪ Run the program with inputs 6000
Time (ms)
of varying size and 5000
composition 4000
3000
▪ Use a method like 2000
System.currentTimeMillis() to get 1000
0
an accurate measure of the 0 50 100
actual running time Input Size
▪ Plot the results
ANALYSIS OF ALGORITHMS 4
© 2004 Goodrich, Tamassia
Limitations of Experiments
▪ It is necessary to implement the algorithm, which may
be difficult
▪ Results may not be indicative of the running time on
other inputs not included in the experiment.
▪ In order to compare two algorithms, the same hardware
and software environments must be used
© 2004 Goodrich, Tamassia ANALYSIS OF ALGORITHMS 5
Theoretical Analysis
▪ Uses a high-level description of the algorithm instead
of an implementation
▪ Characterizes running time as a function of the input
size, n.
▪ Takes into account all possible inputs
▪ Allows us to evaluate the speed of an algorithm
independent of the hardware/software environment
© 2004 Goodrich, Tamassia ANALYSIS OF ALGORITHMS 6
Pseudocode
▪ High-level description of Example: find max element
an algorithm of an array
▪ More structured than Algorithm arrayMax(A, n)
English prose Input array A of n integers
▪ Less detailed than a Output maximum element of A
program
currentMax A[0]
▪ Preferred notation for
describing algorithms for i 1 to n − 1 do
if A[i] currentMax then
▪ Hides program design
issues currentMax A[i]
return currentMax
© 2004 Goodrich, Tamassia ANALYSIS OF ALGORITHMS 7
Pseudocode Details
Control flow Method call
◦ if … then … [else …] var.method (arg [, arg…])
◦ while … do …
Return value
◦ repeat … until …
return expression
◦ for … do …
◦ Indentation replaces braces Expressions
Assignment
Method declaration (like = in Java)
Algorithm method (arg [, arg…]) = Equality testing
Input … (like == in Java)
Output … n2 Superscripts and other
mathematical formatting
allowed
© 2004 Goodrich, Tamassia ANALYSIS OF ALGORITHMS 8
Pseudocode Details
© 2004 Goodrich, Tamassia
Pseudocode Details
© 2004 Goodrich, Tamassia
Pseudocode Details
© 2004 Goodrich, Tamassia
Pseudocode Details
© 2004 Goodrich, Tamassia
Pseudocode
© 2004 Goodrich, Tamassia
© 2004 Goodrich, Tamassia
Try this …..
Read 10 numbers from the user and display the
maximum number ?
Solution
© 2004 Goodrich, Tamassia
Types of Algorithms
◼ Algorithm types we will consider include:
◼ Simple recursive algorithms
◼ Backtracking algorithms
◼ Divide and conquer algorithms
◼ Dynamic programming algorithms
◼ Greedy algorithms
16
Recursion
Objectives
◼ Become familiar with the idea of recursion
◼ Learn to use recursion as a programming tool
◼ Become familiar with the binary search
algorithm as an example of recursion
◼ Become familiar with the merge sort
algorithm as an example of recursion
Overview
Recursion: a definition in terms of itself.
Recursion in algorithms:
◼ Natural approach to some (not all) problems
◼ A recursive algorithm uses itself to solve one or more
smaller identical problems
Recursion in Java:
◼ Recursive methods implement recursive algorithms
◼ A recursive method includes a call to itself
Key Components of a Recursive Algorithm Design
1. What is a smaller identical problem(s)?
Decomposition
2. How are the answers to smaller problems combined to form the
answer to the larger problem?
Composition
3. Which is the smallest problem that can be solved easily (without
further decomposition)?
Base/stopping case
Factorial (N!)
◼ N! = (N-1)! * N [for N > 1]
◼ 1! = 1
◼ 3!
= 2! * 3
= (1! * 2) * 3
=1*2*3
◼ Recursive design:
◼ Decomposition: (N-1)!
◼ Composition: * N
◼ Base case: 1!
factorial Method
public static int factorial(int n)
{
int fact;
if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; // composition
else // base case
fact = 1;
return fact;
}
public static int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}
public static int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}
public static int factorial(int 2)
{
int fact;
if (n > 1)
fact = factorial(1) * 2;
else
fact = 1;
return fact;
}
public static int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}
public static int factorial(int 2)
{
int fact;
if (n > 1)
fact = factorial(1) * 2;
else
fact = 1;
return fact;
}
public static int factorial(int 1)
{
int fact;
if (n > 1)
fact = factorial(n - 1) * n;
else
fact = 1;
return fact;
}
public static int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}
public static int factorial(int 2)
{
int fact;
if (n > 1)
fact = factorial(1) * 2;
else
fact = 1;
return fact;
}
public static int factorial(int 1)
{
int fact;
if (n > 1)
fact = factorial(n - 1) * n;
else
fact = 1;
return 1;
}
public static int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}
public static int factorial(int 2)
{
int fact;
if (n > 1)
fact = 1 * 2;
else
fact = 1;
return fact;
}
public static int factorial(int 1)
{
int fact;
if (n > 1)
fact = factorial(n - 1) * n;
else
fact = 1;
return 1;
}
public static int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}
public static int factorial(int 2)
{
int fact;
if (n > 1)
fact = 1 * 2;
else
fact = 1;
return 2;
}
public static int factorial(int 3)
{
int fact;
if (n > 1)
fact = 2 * 3;
else
fact = 1;
return fact;
}
public static int factorial(int 2)
{
int fact;
if (n > 1)
fact = 1 * 2;
else
fact = 1;
return 2;
}
public static int factorial(int 3)
{
int fact;
if (n > 1)
fact = 2 * 3;
else
fact = 1;
return 6;
}
public static int factorial(int n)
{
int fact;
Execution Trace if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; (composition)
(decomposition) else // base case
fact = 1;
return fact;
}
factorial(4)
factorial(3) 4
public static int factorial(int n)
{
int fact;
Execution Trace if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; (composition)
(decomposition) else // base case
fact = 1;
return fact;
}
factorial(4)
factorial(3) 4
factorial(2) 3
public static int factorial(int n)
{
int fact;
Execution Trace if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; (composition)
(decomposition) else // base case
fact = 1;
return fact;
}
factorial(4)
factorial(3) 4
factorial(2) 3
factorial(1) 2
public static int factorial(int n)
{
Execution Trace int fact;
if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; (composition)
(composition) else // base case
fact = 1;
return fact;
}
factorial(4)
*
factorial(3) 4
*
factorial(2) 3
*
factorial(1)->1 2
public static int factorial(int n)
{
Execution Trace int fact;
if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; (composition)
(composition) else // base case
fact = 1;
return fact;
}
factorial(4)
*
factorial(3) 4
*
factorial(2)->2 3
public static int factorial(int n)
{
Execution Trace int fact;
if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; (composition)
(composition) else // base case
fact = 1;
return fact;
}
factorial(4)
*
factorial(3)->6 4
public static int factorial(int n)
{
Execution Trace int fact;
if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; (composition)
(composition) else // base case
fact = 1;
return fact;
}
factorial(4)->24
Improved factorial Method
public static int factorial(int n)
{
int fact=1; // base case value
if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; // composition
// else do nothing; base case
return fact;
}
Remember:Key to Successful Recursion
◼ if-else statement (or some other branching
statement)
◼ Some branches: recursive call
◼ "smaller" arguments or solve "smaller" versions of
the same task (decomposition)
◼ Combine the results (composition) [if necessary]
◼ Other branches: no recursive calls
◼ stopping cases or base cases
Template
… method(…)
{
if ( … )// base case
{
}
else // decomposition & composition
{
}
return … ; // if not void method
}
Template (only one base case)
… method(…)
{
… result = … ;//base case
if ( … ) // not base case
{ //decomposition & composition
result = …
}
return result;
}
Warning: Infinite Recursion May Cause a Stack
Overflow Error
◼ Infinite Recursion
◼ Problem not getting smaller (no/bad decomposition)
◼ Base case exists, but not reachable (bad base case
and/or decomposition)
◼ No base case
◼ Stack: keeps track of recursive calls by JVM (OS)
◼ Method begins: add data onto the stack
◼ Method ends: remove data from the stack
◼ Recursion never stops; stack eventually runs out of space
◼ Stack overflow error
Binary Search Algorithm
◼ Searching a list for a particular value
◼ sequential and binary are two common algorithms
◼ Sequential search (aka linear search):
◼ Not very efficient
◼ Easy to understand and program
◼ Binary search:
◼ more efficient than sequential
◼ but the list must be sorted first!
Why Is It Called "Binary" Search?
Compare sequential and binary search algorithms:
How many elements are eliminated from the list each
time a value is read from the list and it is not the
"target" value?
Sequential search: only one item
Binary search: half the list!
That is why it is called binary -
each unsuccessful test for the target value
reduces the remaining search list by 1/2.
private int search(int target, int first, int last)
Binary Search {
int location = -1; // not found
Method
if (first <= last) // range is not empty
◼ public
{
find(target) calls
int mid = (first + last)/2;
private
search(target,
if (target == a[mid])
first, last)
location = mid;
◼ returns the index of the else if (target < a[mid]) // first half
entry if the target value is location = search(target, first, mid - 1);
found or -1 if it is not else //(target > a[mid]) second half
found location = search(target, mid + 1, last);
◼ Compare it to the }
pseudocode for the
"name in the phone return location;
book" problem }
Where is the composition?
◼ If no items
◼ not found (-1)
◼ Else if target is in the middle
◼ middle location
◼ Else
◼ location found by search(first half) or search(second half)
Binary Search Example
target is 33
The array a looks like this:
Indices 0 1 2 3 4 5 6 7 8 9
Contents 5 7 9 13 32 33 42 54 56 88
mid = (0 + 9) / 2 (which is 4)
33 > a[mid] (that is, 33 > a[4])
So, if 33 is in the array, then 33 is one of:
5 6 7 8 9
33 42 54 56 88
Eliminated half of the remaining elements from
consideration because array elements are sorted.
Binary Search Example
target is 33
The array a looks like this:
0 1 2 3 4 5 6 7 8 9
Indexes
Contents 5 7 9 13 32 33 42 54 56 88
mid = (5 + 9) / 2 (which is 7)
33 < a[mid] (that is, 33 < a[7]) Eliminate
So, if 33 is in the array, then 33 is one of: half of the
5 6 remaining
33 42 elements
mid = (5 + 6) / 2 (which is 5)
33 == a[mid]
So we found 33 at index 5:
5
33
Binary vs. Sequential Search
◼ Binary Search
◼ log2N + 1 comparisons (worst case)
◼ Sequential/Linear Search
◼ N comparisons (worst case)
◼ Binary Search is faster but
◼ array is assumed to be sorted beforehand
◼ Faster searching algorithms for “non-sorted arrays”
◼ More sophisticated data structures than arrays
◼ Later courses
Recursive Versus Iterative Methods
All recursive algorithms/methods
can be rewritten without recursion.
◼ Iterative methods use loops instead of recursion
◼ Iterative methods generally run faster and use less
memory--less overhead in keeping track of method
calls
So When Should You Use Recursion?
◼ Solutions/algorithms for some problems are inherently
recursive
◼ iterative implementation could be more complicated
◼ When efficiency is less important
◼ it might make the code easier to understand
◼ Bottom line is about:
◼ Algorithm design
◼ Tradeoff between readability and efficiency