Recursion
Dr. Hakim Mellah
Department of Computer Science & Software Engineering
Concordia University, Montreal, Canada
These slides has been extracted, modified and updated from original slides of :
• Data Structures and Algorithms in Java, 5th edition. John Wiley& Sons, 2010. ISBN 978-0-470-38326-1.
• Dr. Hanna’s slides (http://aimanhanna.com/concordia/comp352/index.htm)
Copyright © 2010 Michael T. Goodrich, Roberto Tamassia
All rights reserved
The Recursion Pattern
❑ Recursion: when a method calls itself
❑ Classic example: the factorial function:
n! = 1* 2 * 3 * ··· * (n-1) * n
❑ Recursive definition:
1 if n = 0
f ( n) =
n f (n − 1) else
❑ As a Java method:
// recursive factorial function
public static int recursiveFactorial(int n) {
if (n == 0) return 1; // base case
else return n * recursiveFactorial(n- 1); // recursive case
}
Recursion 2
Content of a Recursive Method
❑ Base case(s)
◼ Also referred to as stopping cases. These are the
cases where the method performs NO more
recursive calls.
◼ 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 method itself.
◼ Each recursive call should be defined so that it
makes progress towards a base case.
Recursion 3
Visualizing Recursion
❑ Example
❑ Recursion trace
return 4*6 = 24 final answer
A box for each
call
◼
recursive call
recursiveFactorial (4)
call return 3*2 = 6
◼ An arrow from each
recursiveFactorial (3)
caller to callee call return 2*1 = 2
◼ An arrow from each recursiveFactorial (2)
callee to caller showing call return 1*1 = 1
return value recursiveFactorial (1)
➔ See Recursion1.java call return 1
Recursion2.java recursiveFactorial (0)
Recursion 4
Recursion & The Stack
❑ A running Java program maintains a private memory
area called the stack, which is used to keep track of
the methods as they are invoked.
❑ Whenever a method is invoked, its information
(parameters, local variables, Program Counter (PC),
…) is placed as one frame into the stack.
❑ The frame is removed from the stack once the method
returns.
Recursion 5
Recursion & The Stack
main()
fun5():
{…
PC = 328
fun1(); m=2
… n=5
} fun1():
Frames PC = 229
fun1() y=7
{…
fun5();
main():
…
PC = 24
} Java Stack x = 10
Recursion 6
Recursion & The Stack
❑ The heap is another memory area that is maintained
for a running program.
❑ The heap is used for dynamic allocation of memory at
runtime (i.e. when new is called to create an object).
❑ Usually the stack and the heap grow against each
other in the memory.
❑ Recursion has hence the potential of overflowing the
stack by quickly consuming all available space.
❑ ➔ See Recursion3.java
Recursion 7
Linear Recursion
❑ Simplest form of recursion, where the method makes at
most one recursive call each time it is invoked.
❑ Very useful when the problem is viewed in terms of first or
last element, plus a remaining set that has the same
structure as the original set.
❑ For instance, obtaining the summation of n values in an
array can be viewed as:
◼ Obtaining the sum of the first n-1 elements plus the value of the
last element;
◼ If the array has only one element, then the summation is that
single value, A[0] .
Recursion 8
Example of Linear Recursion
Algorithm LinearSum(A, n): Example recursion trace:
Input:
An integer array A and an call return 15 + A[4] = 15 + 5 = 20
integer n >= 1, such that A LinearSum (A,5)
has at least n elements call return 13 + A[3] = 13 + 2 = 15
Output: LinearSum (A,4)
The sum of the first n integers call return 7 + A[2] = 7 + 6 = 13
in A LinearSum (A,3)
if n = 1 then call return 4 + A[1] = 4 + 3 = 7
return A[0] LinearSum (A,2)
else call return A[0] = 4
return LinearSum(A, n - 1) + LinearSum (A,1)
A[n - 1]
A
4 3 6 2 5
Recursion 9
Example: Reversing an Array
Algorithm ReverseArray(A, i, j):
Input: An array A and nonnegative integer
indices i and j
Output: The reversal of the elements in A
starting at index i and ending at j
if i < j then
Swap A[i] and A[ j]
ReverseArray(A, i + 1, j - 1)
return
Recursion 10
Defining Arguments for Recursion
❑ In creating recursive methods, it is important
to define the methods in ways that facilitate
recursion.
❑ This sometimes requires additional
parameters to be passed to the method.
❑ For example, we defined the array reversal
method as ReverseArray(A, i, j), not
ReverseArray(A).
Recursion 11
Example: 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 a power function that runs in
O(n) time (for we make n recursive calls).
❑ However, can we do better than this?
Recursion 12
Recursive Squaring
❑ We can derive a more efficient linearly
recursive algorithm by using repeated squaring:
1 if x = 0
p( x, n) = x p( x, (n − 1) / 2) 2 if x 0 is odd
p ( x , n / 2) 2
if x 0 is even
❑ For example,
24 = 2(4/2)2 = (24/2)2 = (22)2 = 42 = 16
25 = 21+(4/2)2 = 2(24/2)2 = 2(22)2 = 2(42) = 32
26 = 2(6/ 2)2 = (26/2)2 = (23)2 = 82 = 64
27 = 21+(6/2)2 = 2(26/2)2 = 2(23)2 = 2(82) = 128.
Recursion 13
Recursive Squaring Method
Algorithm Power(x, n):
Input: A number x and integer n = 0
Output: The value xn
if n = 0 then
return 1
if n is odd then
y = Power(x, (n - 1)/ 2)
return x · y ·y
else
y = Power(x, n/ 2)
return y · y
Recursion 14
Analysis
Algorithm Power(x, n):
Input: A number x and
integer n = 0 Each time we make a
Output: The value xn recursive call we halve
the value of n; hence,
if n = 0 then we make log n recursive
return 1 calls. That is, this
if n is odd then method runs in O(log n)
y = Power(x, (n - 1)/ 2) time.
return x · y · y
else It is important that we
use a variable twice
y = Power(x, n/ 2) here rather than calling
return y · y the method twice.
Recursion 15
Tail Recursion
❑ Tail recursion occurs when a linearly recursive
method makes its recursive call as its last step; as in
the array reversal method.
❑ Such methods can be easily converted to non-
recursive methods (which saves on some resources).
❑ Example:
Algorithm IterativeReverseArray(A, i, j ):
Input: An array A and nonnegative integer indices i and j
Output: The reversal of the elements in A starting at
index i and ending at j
while i < j do
Swap A[i ] and A[ j ] ➔See Factorial.java
i =i+1
j =j-1
return
Recursion 16
Binary Recursion
❑ Binary recursion occurs whenever there are two,
and exactly two, recursive calls for each non-
base case.
❑ Applicable, for instance, when attempting to
solve two different halves of some problem.
❑ Example: Calculating the summation of an n
array elements, can be done by:
◼ Recursively summing the elements in the first half;
◼ Recursively summing the elements in the second half;
◼ Adding the two values.
Recursion 17
Example: Summing Array Elements
Example: Summing n consecutive elements of an array,
starting from a given index i, using binary recursion
Algorithm binarySum(A, i, n)
Input An array A and integers i and n
Output The sum of the n element of A, starting at index i
if n =1 then
return A[i]
return binarySum(A, i, n/2 ) + binarySum(A, i + n/2 ,
n/2 )
Recursion 18
Example: Summing Array Elements
❑ The following provides an example of a
binarySum(0, 8) trace, where each box indicates the
starting index and the number of elements to sum.
❑ Analysis: In every half, the call will be made n-1
times, resulting in a total of 2n - 1 calls ➔ O(n).
❑ However, it should be noted that there is a maximum
of 1 + log2 n active calls at any point of time, which
improves space utilization as we discuss later.
0, 8
0, 4 4, 4
0, 2 2, 2 4, 2 6, 2
0, 1 1, 1 2, 1 3, 1 4, 1 5, 1 6, 1 7, 1
19
Recursion
Example: Fibonacci Numbers
❑ In mathematics, the Fibonacci numbers are the
numbers in the following integer sequence:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...
❑ By definition, the first two Fibonacci numbers are 0
and 1, and each subsequent number is the sum of
the previous two.
❑ Fibonacci numbers can be defined recursively as:
F0 = 0
F1 = 1
Fi = Fi-1 + Fi-2 for i > 1.
Recursion 20
Example: Fibonacci Numbers
❑ Recursive algorithm (first attempt):
Algorithm binaryFib(k):
Input: Nonnegative integer k
Output: The kth Fibonacci number Fk
if k 1 then
return k
else
return binaryFib(k - 1) + binaryFib(k - 2)
Recursion 21
Example: Fibonacci Numbers
❑ Analysis: Let nk be the number of recursive calls
(notice that this is not the value) by binaryFib(k)
◼ n0 = 1
◼ n1 = 1
◼ n2 = n1 + n0 + 1 = 1 + 1 + 1 = 3
◼ n3 = n2 + n1 + 1 = 3 + 1 + 1 = 5
◼ n4 = n3 + n2 + 1 = 5 + 3 + 1 = 9
◼ n5 = n4 + n3 + 1 = 9 + 5 + 1 = 15
◼ n6 = n5 + n4 + 1 = 15 + 9 + 1 = 25
◼ n7 = n6 + n5 + 1 = 25 + 15 + 1 = 41
◼ n8 = n7 + n6 + 1 = 41 + 25 + 1 = 67.
❑ Note that nk at least doubles every other time.
❑ In fact, nk > 2k/2. It is exponential!
Recursion 22
Example: Fibonacci Numbers
❑ The main problem with binaryFib(k) approach is that
the computation of Fibonacci numbers is really a
linearly recursive problem, in spite of its look where Fk
depends on Fk-1 and Fk-2.
❑ The problem is hence not a good candidate for binary
recursion.
❑ We should use linear recursion instead.
Recursion 23
A Better Fibonacci Algorithm
❑ Use linear recursion instead
Algorithm linearFibonacci(k):
Input: A nonnegative integer k
Output: Pair of Fibonacci numbers (Fk , Fk-1)
if k = 1 then
return (k, 0)
else
(i, j) = linearFibonacci(k - 1)
return (i +j, i)
// notice that the values are retuned (however, not both are
displayed)
❑ linearFibonacci makes k-1 recursive calls, so total calls is k.
➔ See LinearFib.java & BinaryFibStack.java
Recursion 24
A Better Fibonacci Algorithm
For instance (note: Fib is short for linearFibonacci),
❑ Fib(2) ➔ (i+j, i) is (1,1) ➔ Will be displaying 1
❑ Fib(3) ➔ (i+j, i) is (2,1) ➔ Will be displaying 2
❑ Fib(4) ➔ (i+j, i) is (3,2) ➔ Will be displaying 3
❑ Fib(5) ➔ (i+j, i) is (5,3) ➔ Will be displaying 5
❑ Fib(6) ➔ (i+j, i) is (8,5) ➔ Will be displaying 8
❑ Fib(7) ➔ (i+j, i) is (13,8) ➔ Will be displaying 13
❑ Fib(8) ➔ (i+j, i) is (21,13) ➔ Will be displaying 21
❑ Fib(9) ➔ (i+j, i) is (34,21) ➔ Will be displaying 34
❑ :
❑ Fib(12) ➔ (i+j, i) is (144,89) ➔ Will be displaying
144
Recursion 25
Binary Recursion
Another Example
❑ The English Ruler:
◼ Print the ticks and numbers like an English ruler
Recursion 26
Slide by Matt Stallmann included with permission.
Example: The English Ruler
drawTicks(length)
Input: length of a ‘tick’
Output: ruler with tick of the given length in
the middle and smaller rulers on either side
drawTicks(length)
if( length > 0 ) then
drawTicks( length − 1 )
draw tick of the given length
drawTicks( length − 1 )
Recursion 27
Recursive Drawing Method
❑ The drawing method is drawTicks (3) Output
based on the following drawTicks (2)
recursive definition
drawTicks (1)
drawTicks (0)
❑ An interval with a drawOneTick (1)
central tick length L >1 drawTicks (0)
consists of: drawOneTick (2)
◼ An interval with a central drawTicks (1)
tick length L−1 drawTicks (0)
◼ A single tick of length L drawOneTick (1)
drawTicks (0)
◼ An interval with a central drawOneTick (3)
tick length L−1 drawTicks (2)
(previous pattern repeats )
Recursion 28
Java Implementation (1)
// draw ruler
public static void drawRuler(int nInches, int majorLength) {
drawOneTick(majorLength, 0); // draw tick 0 and its label
for (int i = 1; i <= nInches; i++) {
drawTicks(majorLength- 1); // draw ticks for this inch
drawOneTick(majorLength, i); // draw tick i and its label
}
}
Note the two
// draw ticks of given length recursive calls
public static void drawTicks(int tickLength) {
if (tickLength > 0) { // stop when length drops to 0
drawTicks(tickLength- 1); // recursively draw left ticks
drawOneTick(tickLength); // draw center tick
drawTicks(tickLength- 1); // recursively draw right ticks
}
}
Recursion 29
Java Implementation (2)
// draw a tick with no label
public static void drawOneTick(int tickLength) {
drawOneTick(tickLength, - 1); // -1 will avoid printing the label
}
// draw one tick
public static void drawOneTick(int tickLength, int tickLabel) {
for (int i = 0; i < tickLength; i++)
System.out.print("-");
if (tickLabel >= 0) System.out.print(" " + tickLabel);
System.out.print("\n");
}
Recursion 30
Multiple Recursion
❑ Multiple recursion:
◼ makes potentially many recursive calls
◼ not just one or two
❑ Motivating example:
◼ Coping folders (directories)
◼ summation puzzles
pot + pan = bib
dog + cat = pig
boy + girl = baby
Recursion 31
Example of Multiple Recursion
Algorithm CopyFolder(folder):
Input: A directory folder, which possibly includes files and subfolders
Output: A copy of the given folder with all its files and subfolders
for all files in folder do
copy file
for all subfolder in folder do
copyfolder(subfolder) // this line is where recursion happens
Recursion 32
Example of Multiple Recursion
Algorithm PuzzleSolve(k,S,U):
Input: Integer k, sequence S, and set U (universe of elements to
test)
Output: Enumeration of all k-length extensions to S using elements
in U without repetitions
for all e in U do
Remove e from U {e is now being used}
Add e to the end of S
if k = 1 then
Test whether S is a configuration that solves the puzzle
if S solves the puzzle then
return “Solution found: ” S
else
PuzzleSolve(k - 1, S,U)
Add e back to U {e is now unused}
Remove e from the end of S
Recursion 33
Slide by Matt Stallmann
included with permission.
Example of Multiple Recursion
cbb + ba = abc a,b,c stand for 7,8,9; not
799 + 98 = 997 necessarily in that order
[] {a,b,c}
[a] {b,c} [b] {a,c} [c] {a,b}
a=7 b=7 c=7
[ab] {c} [ac] {b} [ca] {b} [cb] {a}
a=7,b=8 a=7,c=8 c=7,a=8 c=7,b=8
c=9 b=9 b=9 a=9
[ba] {c} [bc] {a}
b=7,a=8 b=7,c=8 might be able to
c=9 a=9 stop sooner
Recursion 34
Visualizing PuzzleSolve
❑ Notice that the number of concurrently active calls can
still be limited with multiple recursion.
❑ For instance, the number of active calls of CopyFolder
depends on how many nested subfolders may exist at
a time and not on the total number of subfolders in
the directory. Initial call
PuzzleSolve (3,(),{a,b,c})
PuzzleSolve (2,a,{b,c}) PuzzleSolve (2,b,{a,c}) PuzzleSolve (2,c,{a,b})
PuzzleSolve (1,ab,{c}) PuzzleSolve (1,ba,{c}) PuzzleSolve (1,ca,{b})
abc bac cab
PuzzleSolve (1,ac,{b}) PuzzleSolve (1,bc,{a}) PuzzleSolve (1,cb,{a})
acb Recursion bca cba 35