Title: Recursion
1Chapter 8
2Objective
- To learn
- What is recursion?
- Proof by induction
- Basic recursion
- Numerical Applications
- Divide and Conquer
3Introduction to Recursion
- "Normally", we have methods that call other
methods. - For example, the main() method calls the square()
method. - Recursive Method
- A recursive method is a method that calls itself.
main()
square()
compute()
ref cs.nyu.edu/courses/fall07/V22.0102-002/lectur
es/recursion-102-fa07.ppt
4Why use Recursive Methods?
- In computer science, some problems are more
easily solved by using recursive methods. - In this course, will see many of examples of
this. - For example
- Traversing through a directory or file system.
- Traversing through a tree of search results.
- Some sorting algorithms recursively sort data
- For today, we will focus on the basic structure
of using recursive methods.
5Worlds Simplest Recursion Program
- include ltiostreamgt
- using namespace std
- void count (int index)
- cout ltlt index
- if (index lt 2)
- count(index1)
-
- int main ()
- count(0)
- cout ltlt endl
- return 0
This program simply counts from 0-2 012
This is where the recursion occurs. You can see
that the count() method calls itself.
6Visualizing Recursion
- To understand how recursion works, it helps to
visualize whats going on. - To help visualize, we will use a common concept
called the Stack. - A stack basically operates like a container of
trays in a cafeteria. It has only two
operations - Push you can push something onto the stack.
- Pop you can pop something off the top of the
stack. - Lets see an example stack in action.
7Stacks
The diagram below shows a stack over time. We
perform two pushes and one pop.
8
2
2
2
Time 0 Empty Stack
Time 1 Push 2
Time 2 Push 8
Time 3 Pop Gets 8
Time 4 Pop Gets 2
8Stacks and Methods
- When you run a program, the computer creates a
stack for you. - Each time you invoke a method, the method is
placed on top of the stack. - When the method returns or exits, the method is
popped off the stack.
9Stacks and Methods
square()
main()
main()
main()
Time 4 Pop main() returns a value. method
exits.
Time 3 Pop square() returns a value. method
exits.
Time 0 Empty Stack
Time 1 Push main()
Time 2 Push square()
This is called an activation record or stack
frame. Usually, this actually grows downward.
10Stacks and Recursion
- Each time a method is called, you push the method
on the stack. - Each time the method returns or exits, you pop
the method off the stack. - If a method calls itself recursively, you just
push another copy of the method onto the stack. - We therefore have a simple way to visualize how
recursion really works.
11Back to the Simple Recursion Program
- Heres the code again. Now, that we understand
stacks, we can visualize the recursion. - include ltiostreamgt
- using namespace std // Recursion1V0
- void count (int index)
- cout ltlt index
- if (index lt 2)
- count(index1)
-
- int main ()
- count(0)
- cout ltlt endl
- return 0
12Stacks and Recursion in Action
count(2)
count(1)
count(1)
count(0)
count(0)
count(0)
main()
main()
main()
main()
Time 0 Empty Stack
Time 1 Push main()
Time 2 Push count(0)
Time 3 Push count(1)
Time 4 Push count(2)
Times 5-8 Pop everything
Inside count(0) cout ltltindex ? 0 if
(index lt 2) count(index1)
Inside count(1) cout ltltindex ? 1 if (index
lt 2) count(index1)
Inside count(2) cout ltltindex ? 2 if (index lt
2) count(index1) This condition now
fails! Hence, recursion stops, and we proceed to
pop all methods off the stack.
13Recursion, Variation 1
- What will the following program do?
- include ltiostreamgt
- using namespace std // Recursion1V1
- void count (int index)
- cout ltlt index
- if (index lt 2)
- count(index1)
-
- int main ()
- count(3)
- cout ltlt endl
- return 0
14Recursion, Variation 2
- What will the following program do?
- include ltiostreamgt
- using namespace std // Recursion1V0
- void count (int index)
- if (index lt 2)
- count(index1)
- cout ltlt index
-
- int main ()
- count(0)
- cout ltlt endl
- return 0
Note that the print statement has been moved to
the end of the method.
15Recursion, Variation 3
- What will the following program do?
- include ltiostreamgt
- using namespace std // Recursion1V0
- void count (int index)
- if (index gt 2)
- count(index1)
- cout ltlt index
-
- int main ()
- count(3)
- cout ltlt endl
- return 0
16First two rules of recursion
- Base case You must always have some base case
which can be solved without recursion - Making Progress For cases that are to be solved
recursively, the recursive call must always be a
case that makes progress toward the base case.
From Data Structures and Algorithms by Mark Allen
Weiss
17Problem Not working towards base case
- In variation 3, we do not work towards our base
case. This causes infinite recursion and will
cause our program to crash.
18Factorials
- Computing factorials are a classic problem for
examining recursion. - A factorial is defined as follows
- n! n (n-1) (n-2) . 1
- For example
- 1! 1
- 2! 2 1 2
- 3! 3 2 1 6
- 4! 4 3 2 1 24
- 5! 5 4 3 2 1 120
If you study this table closely, you will start
to see a pattern.
19Seeing the Pattern
- Seeing the pattern in the factorial example is
difficult at first. - But, once you see the pattern, you can apply this
pattern to create a recursive solution to the
problem. - Divide a problem up into
- What we know (call this the base case)
- Making progress towards the base
- Each step resembles original problem
- The method launches a new copy of itself
(recursion step) to make the progress.
20Factorials
- Computing factorials are a classic problem for
examining recursion. - A factorial is defined as follows
- n! n (n-1) (n-2) . 1
- For example
- 1! 1 (Base Case)
- 2! 2 1 2
- 3! 3 2 1 6
- 4! 4 3 2 1 24
- 5! 5 4 3 2 1 120
If you study this table closely, you will start
to see a pattern. The pattern is as follows You
can compute the factorial of any number (n) by
taking n and multiplying it by the factorial of
(n-1). For example 5! 5 4! (which
translates to 5! 5 24 120)
21Recursive Solution
Base Case.
- int findFactorial (int number)
-
- if ( number lt1)
- return 1
- else
- return (number findFactorial (number-1))
-
- int main ()
- for (int i 1 i lt 10 i)
- cout ltlt i ltlt "! " ltlt findFactorial(i) ltlt
endl - return 0
Making progress
22Finding the factorial of 3
fact(1)
1
fact(2)
fact(2)
fact(2)
2
fact(3)
fact(3)
fact(3)
fact(3)
fact(3)
6
main()
main()
main()
main()
main()
main()
Time 3 Push fact(2)
Time 4 Push fact(1)
Time 7 Pop fact(3) returns 6.
Time 2 Push fact(3)
Time 6 Pop fact(2) returns 2.
Time 5 Pop fact(1) returns 1.
Inside findFactorial(1) if (number lt 1) return
1 else return (1 factorial (0))
Inside findFactorial(3) if (number lt 1) return
1 else return (3 factorial (2))
Inside findFactorial(2) if (number lt 1) return
1 else return (2 factorial (1))
23Tail recursion
- Tail recursion is when the last line of a method
makes the recursive call. - In this case, you have multiple active stack
frames which are unnecessary because they have
finished their work. - It is easy to rid you program of this type of
recursion. These two steps will do so - Enclose the body of the method in a while loop
- Replace the recursive call with an assignment
statement for each method argument. - Most compilers do this for you. Note I said
"most".
24Revisit recursive factorial solution
Just follow our two steps
int findFactorial (int number) if
(numberlt1) return 1 else
return (number findFactorial
(number-1)) int main () for (int i
1 i lt 10 i) cout ltlt i ltlt "! " ltlt
findFactorial(i) ltlt endl return 0
int findFactorial (int number) int answer
1 while ( number gt 1) answer
answer number number --
return answer int main () for
(int i 1 i lt 10 i) cout ltlt i ltlt "! "
ltlt findFactorial(i) ltlt endl return 0
25Recursive Function Call
- A recursion function is a function that either
directly or indirectly makes a call to itself. - But, we need to avoid making an infinite sequence
of function calls (infinite recursion). - A recursive solution to a problem must be written
carefully - The idea is for each successive recursive call to
bring you one step closer to a situation in which
the problem can easily be solved - This easily solved situation is called the base
case - Each recursive algorithm must have at least one
base case, as well as a general (recursive) case
26Mathematical Induction
Let p(n) denote the statement involving the
integer variable n. The Principle of
Mathematical Induction states If p(1) is true
and, for some integer K gt1 , p(k1) is
true whenever p(k) is true then p(n) is
true for all ngt1 .
27A recursive definition
- int s (int n)
-
- if (n 1)
- return 1
- else
- return s(n-1) n
28Printing number in 10 Base
- void printDecimal (int n)
-
- if (ngt10)
-
- printDecimal(n/10)
-
- cout.put(0n10)
-
29Printing number in Any Base
- const string DIGIT_TABLE "0123456789abcdef"
- const int MAX_BASE DIGIT_TABLE.length( )
- void printIntRec( int n, int base )
-
- if( n gt base )
-
- printIntRec( n / base, base )
-
- cout ltlt DIGIT_TABLE n base
-
30General format for Many Recursive Functions
- if (some easily-solved condition) // base
case - solution statement
- else // general case
- recursive function call
31When a function is called...
- A transfer of control occurs from the calling
block to the code of the function--it is
necessary that there be a return to the correct
place in the calling block after the function
code is executed this correct place is called
the return address - When any function is called, the run-time stack
is used on this stack is placed an activation
record for the function call
32Stack Activation Frames
- The activation record contains the return address
for this function call, and also the parameters,
and local variables, and space for the functions
return value, if non-void - The activation record for a particular function
call is popped off the run-time stack when the
final closing brace in the function code is
reached, or when a return statement is reached in
the function code - At this time the functions return value, if
non-void, is brought back to the calling block
return address for use there
33Run-Time Stack Activation Records x Func(5,
2) // original call at instruction 100
FCTVAL
? result
? b 2
a 5 Return
Address 100
original call at instruction 100 pushes on this
record for Func(5,2)
34Run-Time Stack Activation Records x Func(5,
2) // original call at instruction 100
FCTVAL ?
result ?
b 1
a 5 Return Address 50
FCTVAL ?
result 5Func(5,1) ?
b 2 a
5 Return Address 100
call in Func(5,2) code at instruction 50 pushes
on this record for Func(5,1)
35Run-Time Stack Activation Records x Func(5,
2) // original call at instruction 100
call in Func(5,1) code at instruction 50 pushes
on this record for Func(5,0)
FCTVAL ?
result ?
b 0 a
5 Return Address 50
FCTVAL ?
result 5Func(5,0) ?
b 1 a
5 Return Address 100
FCTVAL ?
result 5Func(5,1) ?
b 2 a
5 Return Address 100
36Run-Time Stack Activation Records x Func(5,
2) // original call at instruction 100
FCTVAL 0
result 0
b 0 a
5 Return Address 50
FCTVAL ?
result 5Func(5,0) ?
b 1 a
5 Return Address 50
FCTVAL ?
result 5Func(5,1) ?
b 2 a
5 Return Address 100
record for Func(5,0) is popped first with its
FCTVAL
record for Func(5,1)
record for Func(5,2)
37Run-Time Stack Activation Records x Func(5,
2) // original call at instruction 100
FCTVAL 5
result 5Func(5,0) 5 0
b 1
a 5 Return Address
100 FCTVAL
? result 5Func(5,1) ?
b 2
a 5 Return Address
100
record for Func(5,1) is popped next with its
FCTVAL
record for Func(5,2)
38Run-Time Stack Activation Records x Func(5,
2) // original call at instruction 100
FCTVAL
10 result 5Func(5,1)
55 b 2
a 5 Return
Address 100
record for Func(5,2) is popped last with its
FCTVAL
39Too much recursion Can Be Dangerous
Fibonacci numbers. Long fib (int n) If (n
lt1) return n Else return
fib(n-1) fib(n-2) This definition will lead
to exponential running time.
40Tree
- Tree is a fundamental structure in computer
science. - Recursive definition A tree is a root and zero
or more nonempty subtrees. - Nonrecursive definition A connected graph
without loop.
41Some more examples
- Binary Search
- template ltclass Comparablegt int binarySearch(
const vectorltComparablegt a, const Comparable
x, int low, int high ) -
- if( low gt high )
- return NOT_FOUND
- int mid ( low high ) / 2
- if( a mid lt x )
- return binarySearch( a, x, mid 1,
high ) - else if( x lt a mid )
- return binarySearch( a, x, low, mid
- 1 ) - else return mid
-
42Numerical Applications
- Modular exponentiation Compute XN(mod P)
- Greatest common divisor Compute gcd(A, B)
43Modular exponentiation
- Three rules
- If AB (mod N), then for any C, AC BC (mod N)
- If AB (mod N), then for any D, AD BD (mod N)
- If AB (mod N), then for any positive P, APBP
(mod N)
44Continue
- If N is even, XN (XX)N/2
- If N is odd, XN X(XX)N/2
- template ltclass HugeIntgt HugeInt power( const
HugeInt x, const HugeInt n , const HugeInt
p ) -
- if( n 0 )
- return 1
- HugeInt tmp power( ( x x ) p, n / 2,
p ) - if( n 2 ! 0 )
- tmp ( tmp x ) p
- return tmp
-
45Greatest common divisor
- One rule
- gcd(A, B) gcd(A-B, B) or gcd(B, A mod B)
- Example
- gcd(71, 25)?gcd(25, 21) ?gcd(21, 4) ?gcd(4, 1)
?gcd(1, 0) 1 - template ltclass HugeIntgt
- HugeInt gcd( const HugeInt a, const HugeInt
b) -
- if (b 0)
- return a
- else
- return gcd(b, ab)
46Divide and Conquer
- Given an instance of the problem to be solved,
split this into several, smaller, sub-instances
(of the same problem) independently solve each of
the sub-instances and then combine the
sub-instance solutions so as to yield a solution
for the original instance.
47The Maximum Contiguous Subsequence Sum Problem
Consider 4, -3, 5, -2, -1, 2, 6, -2 Case 1 It
resides entirely in the first half. Case 2 It
resides entirely in the second half. Case 3 It
begins in the first half but ends in the
second half.
48- template ltclass Comparablegt Comparable
maxSubSum( const vectorltComparablegt a, int
left, int right ) -
- Comparable maxLeftBorderSum 0,
maxRightBorderSum 0 - Comparable leftBorderSum 0, rightBorderSum
0 - int center ( left right ) / 2
- if( left right ) // Base Case.
- return a left gt 0 ? a left
0 - Comparable maxLeftSum maxSubSum( a, left,
center ) - Comparable maxRightSum maxSubSum( a,
center 1, right ) - for( int i center i gt left i-- )
- leftBorderSum a i
- if( leftBorderSum gt maxLeftBorderSum
) - maxLeftBorderSum
leftBorderSum -
- for( int j center 1 j lt right j )
- rightBorderSum a j
- if( rightBorderSum gt
maxRightBorderSum ) - maxRightBorderSum
rightBorderSum -
49Analysis of Divide and Conquer Recurrence
- T(N) 2T(N/2) O(N)
- Result
- T(N)/N T(1)/1 logN
50Common Errors
- Forgetting a base case
- Overlapping recursive calls
- Using recursion in place of a simple loop.
51Homework
- Question
- 8.27, due next Wed. (Feb. 11)
- Print your program
- Finish reading chapter 8