Recursion
© 2013 Goodrich, Tamassia, Goldwasser Recursion 1
The Recursion Pattern
One way to do repetition – loops
Another way – recursion – very powerful tool
Recursion: when a method calls itself 1/more times
Classic example--the factorial function:
n! = 1· 2· 3· ··· · (n-1)· n
Recursive definition: n * f (n 1) if n 2
f ( n)
1 else
As a Python method (runs at O(n)):
def fact(n):
Fact (4) = 4*3*2*1
Fact(4) = 4 * ( 3 * ( 2 * 1) if n>=2:
return n*fact(n-1)
else return 1
© 2013 Goodrich, Tamassia, Goldwasser Recursion 2
Properties of a Recursive
Method
Recursive solutions have 3 rules / properties:
Base case(s) – the terminating case – when to stop
Values of the input variables for which we perform no
recursive calls are called base cases (there should be at
least one base case).
Every possible chain of recursive calls must eventually
reach a base case.
Recursive calls
Calls to the current method.
Each recursive call should be defined so that it makes
progress towards a base case.
Progress must be made towards the base
© 2013 Goodrich, Tamassia,
Recursion Goldwasser 3
Recursive Trace / Call Trees
Used to develop / evaluate
a recursive function
A box for each call
Arrows show flow of
execution
Dotted arrows show
returned values
E.g. shows fact(5)
def fact(n):
if n>=2:
return n*fact(n-1)
else return 1
© 2013 Goodrich, Tamassia,
Recursion Goldwasser 4
Recursive Trace / Call Trees
Draw the call tree for:
def main():
y=foo(3)
bar(2)
def foo(x):
if x%2 != 0:
return 0
else return x + foo(x-1)
def bar(n):
if n>0:
print (n)
bar(n-1)
main()
© 2013 Goodrich, Tamassia,
Recursion Goldwasser 5
How does recursion work?
When a function is called the flow of execution from where the call is made
is interrupted and execution jumps to the function.
When the function ends, execution returns to where it was interrupted – in
the calling program
How does the compiler know where to return?
When a function is called, an activation record is created with function
info
One piece of info is the return address – location of next instruction to be
executed when the function terminates
A separate activation record is created for EACH function call – even to the
same function
© 2013 Goodrich, Tamassia,
Recursion Goldwasser 6
Recursive Binary Search
Remember that when searching for an item
we repeatedly compare to the middle item
If its not found, we split the sequence in half – either top or bottom
half
The same is repeated for this smaller sequence
Reminder of our binary search – no recursion
def binary_search(A,val):
lower = 0 upper = len(A)-1
while lower <= upper:
mid = (lower+upper)//2
if val == A[mid]:
return True
elif val < A[mid]:
upper = mid-1
else:
lower = mid+1
return False
© 2013 Goodrich, Tamassia,
Recursion Goldwasser 7
Binary Search – a classic alg
Search for an integer, target, in an ordered list, arr.
def binarySearch(arr, low, high, target):
# Check base case
if low<=high:
mid = (high + low) // 2
# If element is present at the middle itself
if arr[mid] == target:
return mid
# If element is smaller than mid, then it can only
# be present in left / lower subarray
elif arr[mid] > target:
return binarySearch(arr, low, mid - 1, target)
# Else the element can only be present in right subarray
else:
return binarySearch(arr, mid + 1, high, target)
else:
# Element is not present in the array
return -1
© 2013 Goodrich, Tamassia, Goldwasser Recursion 8
Visualizing Binary Search
We consider three cases:
If the target equals data[mid], then we have found the target.
If target < data[mid], then we recur on the first half of the
sequence.
If target > data[mid], then we recur on the second half of the
sequence.
© 2013 Goodrich, Tamassia, Goldwasser Recursion 9
Analyzing Binary Search
Runs in O(log n) time.
Because each recursive call divides the
search region in half; there can be at most
log n levels.
© 2013 Goodrich, Tamassia, Goldwasser Recursion 10
Types of Recursion
Linear Recursion
The body of the function makes at most ONE new call to the
recursive function (binary search & factorial)
Binary search runs at O(logn) time constraint
Other linear recursion runs at O(n) time – e.g. factorial
Binary Recursion
The body of the function makes TWO recursive calls to the
recursive function (Fibonacci)
Often runs at O(n2)
Multiple Recursion
The body of the function makes more than TWO calls to the
recursive function
Often runs at O(nk), where k is the number of recursive calls in
the function
© 2013 Goodrich, Tamassia, Goldwasser Recursion 11
Designing Recursion algorithms
Test for base cases
Begin by testing for a set of base cases (there should be
at least one).
Every possible chain of recursive calls must eventually
reach a base case, and the handling of each base case
should not use recursion.
Recur
Perform 1/more recursive calls
This step may have a test that decides which of several
possible recursive calls to make, but it should ultimately
make just one of these calls
Define each possible recursive call so that it makes
progress towards a base case.
© 2013 Goodrich, Tamassia, Goldwasser Recursion 12
Defining Arguments for Recursion
In creating recursive methods, it is important to define
the methods in ways that facilitate recursion.
This sometimes requires we define additional
parameters that are passed to the method.
For example, we defined the binary search method as
binary_search(Array, low, high, target), not
binary_search(Array, target)
© 2013 Goodrich, Tamassia, Goldwasser Recursion 13
Computing Powers
The power function, p(x,n)=xn, can be
defined recursively:
ì 1 if n = 0
p ( x , n) = í
î x × p( x, n - 1) else
This leads to an power function that runs in
O(n) time (for we make n recursive calls).
Power(2,5) =return 2* power(2,4)
def power(x,n): return 2* 2* power(2,3)
if n == 0: return 2*2*2*power(2,2)
return 1 return 2*2*2*2*power(2,1)
else: return 2*2*2*2*2*power(2,0
return x* power(x, n-1) return 2*2*2*2*2*1
32
© 2013 Goodrich, Tamassia, Goldwasser Recursion 14
Tail Recursion
Tail recursion occurs when a linearly recursive
method makes its recursive call as its last step.
The binary_search method is an example.
Such methods can easily be converted to non-
recursive methods (which saves on some resources).
def binarySearch(arr, low, high, target):
if low<=high:
mid = (high + low) // 2
if arr[mid] == target:
return mid
elif arr[mid] > target:
return binarySearch(arr, low, mid - 1, target)
else:
return binarySearch(arr, mid + 1, high, target)
else:
return -1
© 2013 Goodrich, Tamassia, Goldwasser Recursion 15
Tail Recursion
def binary_search(A,target):
lower =
upper = len(A)-1
while lower <= upper:
mid = (lower+upper)//2
if target == A[mid]:
return True
elif target < A[mid]:
upper = mid-1
else:
lower = mid+1
return False
© 2013 Goodrich, Tamassia, Goldwasser Recursion 16
Binary Recursion- Fibonacci Numbers
Fibonacci numbers are defined recursively:
F0 = 0
F1 = 1
Fi = Fi-1 + Fi-2 for i > 1.
Recursive algorithm (first attempt):
A call to the function fib(n) – has the
recursive call return fib(n-1) + fib(n-2)
So for each n, fib is called twice
Therefore the time complexity is O(n2)
© 2013 Goodrich, Tamassia, Goldwasser Recursion 17
Binary Recursion- Fibonacci Numbers
def fib(n): # find the nth fibonacci number
if n > 1:
return fib(n-1)+fib(n-2)
else:
return n
© 2013 Goodrich, Tamassia, Goldwasser Recursion 18