CompSci 105 – Recursion
TOPIC: Recursion
TEXT BOOK (Walls and Mirrors):
Recursion: The Mirrors
Definition ───────────────────────────────────────────────────
Recursion (or self-reference) is the concept of defining a data structure or an algorithm in
terms of itself.
Examples ───────────────────────────────────────────────────
Consider how we could define a data structure for a list (we will look at data structures and lists in
much more detail later):
a list is either empty, or it contains an element followed by a list
Or how we could define an algorithm for computing the sum of the integers from 0 to n
if n is 0, the answer is 0
otherwise, compute the sum of integers from 0 to n-1, and then add n
Recursive definitions ─────────────────────────────────────────────
Many different data structures and algorithms have natural recursive definitions. In order for a
definition of this type to be valid, it must consist of at least two parts:
1)
2)
one or more base cases (which do not contain any self-reference)
one or more recursive cases (which do contain self-reference)
For example consider the following recursive definition for the set of natural numbers: {1, 2, 3, 4, ... }:
Natural numbers
base case:
recursive case:
1 is a natural number
i is a natural number, if i-1 is a natural number
Many mathematical operations also have elegant recursive definitions:
Sum of the first n natural numbers
base case:
sum(0) = 0
recursive case:
sum(n) = n + sum(n-1)
Factorial
base case:
recursive case:
0! = 1
n! = n × (n-1)!
The main focus of this topic is on recursive algorithms – we will look at recursive data structures later
on.
Page 1 of 20
CompSci 105 – Recursion
Writing recursive methods ─────────────────────────────────────────
Let’s start with a very simple, non-recursive method. The method below computes the sum of the
integers from 0 up to and including the parameter n:
public int sum(int n) {
int result = 0;
for (int i = 0; i <= n; i++)
result += i;
return result;
}
We know the sum() method works correctly, because the code is very simple to follow. We could
define another method, called sum2(), to do exactly the same thing, but this new method will make
use of the method sum() in the following way:
public int sum2(int n) {
return n + sum(n-1);
}
It is very easy to follow this code as well. To compute the sum of the integers from 0 to n, it uses the
sum() method (which we know works correctly) to compute the sum of the integers from 0 to n-1,
and then simply adds n. So the sum2() method will also work correctly. Note that the above method
is not recursive – because although it does call a method, it does not call itself.
But now what we have are two correctly working methods, sum() and sum2(), that do exactly the
same thing – they both compute the sum of the integers from 0 to n. So presumably, anywhere we call
the sum()method, we could equally well call sum2() and get the same result.
The reason the sum2() method calls the sum() method, is to compute the sum of the integers from
0 to n-1. But computing sums is exactly what the sum2() method does! Why don’t we replace that
method call to sum() with a call to sum2()? We would then have:
public int sum2(int n) {
return n + sum2(n-1);
}
This is almost a correct recursive method. The problem is, it will never finish! It will keep calling
itself with smaller and smaller values (which will eventually become negative) until the JVM runs out
of stack space. The thing that is missing is the base case. The base case for this problem will be that
the result 0 should be returned if the value of the parameter n is 0.
We can now write our first recursive method:
public int sum2(int n) {
if (n == 0)
return 0;
else
Page 2 of 20
CompSci 105 – Recursion
return n + sum2(n-1);
}
Page 3 of 20
CompSci 105 – Recursion
Incorrect recursion ──────────────────────────────────────────────
As indicated by the previous example, a recursive method must have a base case – without one the
method will be incorrect and will not finish.
For example, the method below is incorrect because there is no base case defined. It is easy to see
that this method will not finish:
public void bad() {
System.out.println(“very bad”);
bad();
}
But just having a base case is not enough. Consider the following incorrect method:
public int sum(int n) {
if (n == 0)
return 0;
else
return sum(n+1) – (n+1);
}
The logic here seems OK: the sum of the integers from 0 to n is the sum of the integers from 0 to n+1
with (n+1) subtracted. However, this method will not work. Think about what happens as the method
executes. Let’s say we call sum(10). Successive calls to the sum() method will be passed larger
and larger parameters: sum(11), sum(12),… – in fact, the value of n will never be 0 and so the
base case is never reached.
So, although it is important to have a base case, it is equally important that the recursive step makes
progress towards that base case.
Important ───────────────────────────────────────────────────
In summary, a recursive method must:
have a base case
make progress towards that base case on each recursive step (by tackling a problem of a
smaller size), so that it is guaranteed to reach the base case eventually
For example, the problem of calculating the sum of the integers from 0 to n has:
a base case:
i0
and a recursive case:
i n i 0 i
0
i 0
n
i 0
sum(0) = 0
n 1
sum(n) = n + sum(n-1)
Notice that in the recursive case, the solution to the main problem (of size n) is expressed in terms of a
solution to a slightly smaller problem (of size n-1). The solution to this slightly smaller problem could
then be expressed in terms of a solution to an even smaller problem, and so on, until eventually the
required solution would be defined by the base case. Having the recursive cases make progress
towards the base case is necessary for recursion to work.
Page 4 of 20
CompSci 105 – Recursion
Examples ───────────────────────────────────────────────────
Factorial
The factorial of a number, n, is the product of all integers from 1 up to and including n. The factorial
of 0, is defined to be 1. In other words:
0! = 1
n! = n * (n-1) * (n-2) * (n-3) * ... * 3 * 2 * 1
We can easily write a recursive definition of the factorial function:
factorial(0) = 1
factorial(n) = n * factorial(n-1)
If we were to write a method to compute the factorial of a number, we could easily do it iteratively
(using a loop), or recursively. The recursive code follows easily from the definition:
public static int factorial(int n) {
if (n == 0)
return 1;
else
return n * factorial(n-1);
}
Printing
Let’s design a recursive method that prints out all the integers from n down to 0, inclusive – starting at
n and counting down to 0.
Base case:
when n is 0, all we need to print out is 0
Recursive case: when n is greater than 0, we should print out n, and then recursively
print the integers less than n.
Again, the code follows easily from this definition:
public void print(int n) {
if (n == 0)
System.out.println(0);
else {
System.out.println(n);
print(n-1);
}
}
What would be the output from the following method call:
print(4)
Page 5 of 20
CompSci 105 – Recursion
The first thing that would happen, is the value "4" would be printed. Then the method would be called
recursively with the parameter n being 3. The result of this would be that "3" is printed, and then the
method is called again.
The last output would be the value "0" when the base case is reached.
Therefore, the output of:
print(4)
would be:
4
3
2
1
0
What do you think would happen if we made the recursive call in the else statement before the
println() statement? In other words, if the code is changed to:
public static void print(int n) {
if (n == 0)
System.out.println(0);
else {
print(n-1);
System.out.println(n);
}
}
How does this change things? Well, the base case is the same as before:
Base case:
when n is 0, all we need to print out is 0
But now, the recursive case is different:
Recursive case: when n is greater than 0, we should call the method recursively to print the
integers less than n, and then print out n.
This now prints the numbers from 0 up to the value of n. For example,
print(4)
now produces:
0
1
2
3
4
Page 6 of 20
CompSci 105 – Recursion
Page 7 of 20
CompSci 105 – Recursion
Fibonacci Sequence
In 1202, Fibonacci investigated how fast rabbits could breed in ideal circumstances. He posed the
following problem:
Suppose a newly-born pair of rabbits, one male, one female, are put in a field. Rabbits are able
to mate at the age of one month so that at the end of its second month a female can produce
another pair of rabbits. Suppose that our rabbits never die and that the female always
produces one new pair (one male, one female) every month from the second month on.
The following diagram shows how the rabbit population would grow. Each row in the diagram shows
the total number of pairs of rabbits for a given month:
The Fibonacci sequence lists the number of pairs of rabbits in the population every month:
1
1
2
3
5
8
13
21
34
55
89
144
233 ...
Every number is the sum of the previous two numbers in the sequence. This sequence can be defined
recursively as follows:
There are two base cases:
fibonacci(1) = 1
fibonacci(2) = 1
and the recursive case is:
fibonacci(n) = fibonacci(n-1) + fibonacci(n-2)
ie.
the number of
rabbits in any
given month
=
the number of
rabbits in the
month before
+
the number of rabbits there were two
months ago (which is the number of
newly born rabbits this month)
Using this definition, we can write a method to calculate the nth Fibonacci number in the sequence as
follows:
public static int fibonacci(int n) {
if (n <= 2)
return 1;
else
Page 8 of 20
CompSci 105 – Recursion
return fibonacci(n-1) + fibonacci(n-2);
}
Page 9 of 20
CompSci 105 – Recursion
Thinking recursively ─────────────────────────────────────────────
When you encounter recursion for the first time, it can seem unusual and difficult – especially if you
are writing your own recursive methods for the first time. Not all problems are suited to a recursive
solution, but for those that are it is important to think about solving the problem in a “recursive way”.
This means deciding on a base case, which usually is just a trivial solution to the problem, for a small
problem size of perhaps 0 or 1.
Then you need to think about the recursive step. What you should try to do is reduce the problem size
and then make a recursive call to the method you are writing to solve this smaller problem – in other
words you assume your method works for solving the smaller problem and you use that solution to
solve the original, larger problem. You do not have to worry about exactly how this smaller problem
will be solved, just trust that recursion will do the job.
As an example of this thinking process, consider the problem of calculating the sum of the elements in
an array of integers.
...
nums
0
1
2
3
4
n
Assume you are to write a recursive method to calculate the sum of the elements of this array. Firstly,
you would need to decide on the method header. You might try:
public int sum(int[] nums)
but there is a problem with this. When you call this method recursively, you would be passing in
exactly the same information each time – a reference to the array. You may have noticed with the
other recursive methods we have seen so far, one of the parameters has changed on each recursive call
to indicate how much progress has been made. We will introduce another parameter, in addition to the
array reference, to indicate how many of the elements in the array we would like to sum. So our
method header will look like:
public int sum(int[] nums, int n)
This method will return the sum of the first n elements of the array nums. For example, to return the
sum of the first four elements of an array (at indices 0, 1, 2 and 3) we would call:
sum(nums, 4);
If we want to return the sum of all the elements in the array, we would call this method using:
sum(nums, nums.length);
How can we write the body of this method? We first need to decide on a base case and a recursive
step. The base case is easy:
when n is 0, there will be no elements in the sum. In other words, the sum of the first zero
elements in the array must be 0.
Page 10 of 20
CompSci 105 – Recursion
Thus, so far we have:
public int sum(int[] nums, int n) {
if (n == 0)
return 0;
else {
...
}
}
The recursive step is a little harder. We need to try to reduce the size of the problem so that we can
solve the smaller problem with a recursive call. In this case, we are trying to calculate the sum of the
first n elements of an array. An obvious reduction in the problem size would be to compute the sum of
the first (n-1) elements of the array. Is this a good choice?
Well, given the solution to the sum of the first (n-1) elements, it is trivial to calculate the sum of the
first n elements – we just add the value of element n. It is also a good choice because it makes
progress towards the base case which returns the sum of the first zero elements of the array.
So the recursive step now looks something like:
the sum of the first n elements = value of the nth element
+ the sum of the first (n-1) elements
The completed method then looks like:
public int sum(int[] nums, int n) {
if (n == 0)
return 0;
else {
return nums[n-1] + sum(nums, n-1);
}
}
NOTE:
due to the way arrays are indexed, nums[n-1] is the nth element
Assume the following array is declared:
int[] x = {5, 2, 8};
What would be returned by the following method calls:
sum(x, 0);
sum(x, 1);
would return 0
would return 5
Page 11 of 20
CompSci 105 – Recursion
would return 7
would return 15
sum(x, 2);
sum(x, 3);
NOTE:
if we wanted to, we could implement the previous sum() method with a single parameter –
just the array (nums) on its own without the int parameter (n), and the method’s base case
would check the size of the array (for example, the base case would be when the size of nums
is 0 or 1). However, this would mean we would have to create a smaller array to pass to the
method each time we call it recursively – using the additional int parameter is the easiest
solution.
Efficiency ───────────────────────────────────────────────────
Is recursion an efficient way of solving problems? Later we will look at formal ways of comparing
algorithms for efficiency, but for now we will just talk informally.
In general, recursion is not very efficient – method calls require certain overhead and recursive
solutions often make many method calls. Why would we use a recursive method when a loop will do?
some compilers actually convert certain recursive code into loops
sometimes the elegance of the code is more important than the efficiency
recursion may be the best way
Let's look at the Fibonacci example again:
public static int fibonacci(int n) {
if (n <= 2)
return 1;
else
return fibonacci(n-1) + fibonacci(n-2);
}
This is actually extremely inefficient. How many method calls will there be in total to calculate the
5th Fibonacci number?
fibonacci(5):
5
3
4
2
3
2
2
1
1
As we can see from the above diagram, 9 method calls are required. How about the 44th Fibonacci
number? See if you can complete this diagram:
Page 12 of 20
CompSci 105 – Recursion
44
perhaps you should estimate how long
this will take before you attempt it!
Page 13 of 20
CompSci 105 – Recursion
Recursive drawings ─────────────────────────────────────────────
We can visualise some interesting examples of recursion by looking at programs which draw certain
recursively defined curves. A recursively defined curve, or fractal, is a geometric shape that can be
subdivided in parts, each of which is (at least approximately) a reduced-size copy of the whole.
Koch curve
For example, we can draw a recursive curve called a Koch curve as follows. Starting with a simple
line, we can say that this is our curve at order 0:
order 0
To draw the curve at the next level, or order 1, we simply draw the order 0 curve (which is a straight
line) four times. Each time we draw the order 0 curve, it is 1/3 the size of the original, and we
arrange the four order 0 curves in the pattern below, where the angle at the top of the point is 60
degrees:
order 1
Drawing the curve at the next level, or order 2, requires the same steps, except this time it is four
copies of the order 1 curve that are drawn, reduced in size and arranged in the same pattern as before.
Another way of thinking of this is that the order 2 curve is drawn by replacing each line segment in
the order 1 curve by a complete copy of itself (scaled down in size to 1/3):
order 2
We can repeat this process and draw Koch curves of higher orders. The diagram below shows Koch
curves of the first 5 orders:
Page 14 of 20
CompSci 105 – Recursion
Even for curves of high order, if you magnify them and look at a small part of them very closely, their
shape is the same as the original order 1 curve.
C curve
Another type of recursively defined curve is the C curve. Here, each line is replaced by a pair of lines
at right angles which are 1 2 times the size of the previous lines. A selection of orders are shown
below:
Dragon curve
A dragon curve is like a C curve, but slightly more complicated because every second curve is
reversed. The shape of a dragon curve actually models the side-on shape of a piece of paper that has
been folded and then opened out. The order n curve has the shape of a piece of paper that has been
folded n times:
Sierpinski
A Sierpinski curve can be constructed in several ways, one of which is a simple point plotting
process. Recursively, it can be constructed by drawing triangles of smaller sizes to form a pattern.
Each curve contains three copies of the curve of the previous order, each half the size of the original,
stacked on top of one another to form a triangle. The first 5 orders are shown below:
Page 15 of 20
CompSci 105 – Recursion
Pentadentrite
A more complicated base shape is shown below:
In the diagram above, the angle A is 11.8186 degrees and the distance r is 0.34845, assuming the
distance in a straight line from the start to the end of the shape is 1. This is the base shape for a
recursive curve called a pentadentrite. For each order, the curve is formed by replacing each line
segment in the curve of the previous order by a scaled down copy of this shape. The first few orders
are shown below:
This shape is called a pentadentrite because when drawn at a very high order, five copies of itself fit
perfectly together to form the following shape:
Page 16 of 20
CompSci 105 – Recursion
How to draw recursive curves ───────────────────────────────────────
All of these curves can be drawn using a turtle. A turtle gets its name from the Logo programming
language, which was a simple language for drawing shapes – the turtle was the actual object that did
the drawing and could be controlled with commands such as:
penUp
penDown
move
turn
You can think of the turtle as if holding a pen – if the pen is up, and the turtle moves, a line will be
drawn. The turtle can turn from side to side before moving, and thus is able to draw certain shapes.
The Turtle class
A simple Turtle class would define the following methods:
a constructor (to which the initial position of the turtle is specified):
public Turtle(double x, double y)
the standard turtle drawing commands:
public void penUp()
public void penDown()
public void turn(double degrees)
public void move(double distance)
useful accessor methods:
public void setDirection(double angle)
public void setPosition(double x, double y)
public void setGraphics(Graphics g)
Using the Turtle
Using such a Turtle class, we can create a Turtle object:
Turtle t = new Turtle(100, 100);
and then tell it to move and turn to draw the recursive curves. Let’s start by looking at the
pseudocode for drawing the Koch curve. The method will require two parameters – one is the order
of the curve to draw, and the other is the size. The pseudocode follows:
koch(order, size)
if order is 0
move size units
else
draw koch(order-1) at size/3 units
turn 60 degrees
draw koch(order-1) at size/3 units
turn -120 degrees
draw koch(order-1) at size/3 units
turn 60 degrees
Page 17 of 20
CompSci 105 – Recursion
draw koch(order-1) at size/3 units
Page 18 of 20
CompSci 105 – Recursion
This can easily be converted to source code:
public void koch(int order, double size) {
if (order == 0) {
t.move(size);
}
else {
koch(order-1, size/3);
t.turn(60);
koch(order-1, size/3);
t.turn(-120);
koch(order-1, size/3);
t.turn(60);
koch(order-1, size/3);
}
}
We can do the same for the other recursive curves. For example, the C curve pseudocode would look
like:
c(order, size)
if order is 0
move size units
else
turn -45 degrees
draw c(order-1) at size/sqrt(2) units
turn 90
draw c(order-1) at size/sqrt(2) units
turn -45 degrees
and the source code becomes:
public void c(int order, double size) {
if (order == 0) {
t.move(size);
}
else {
t.turn(-45);
c(order-1, size/Math.sqrt(2));
t.turn(90);
c(order-1, size/Math.sqrt(2));
t.turn(-45);
}
}
Writing the source code for the other recursive curves described above is a good exercise.
Page 19 of 20
CompSci 105 – Recursion
Recursive Curves Application ───────────────────────────────────────
An application has been written for you that you can use to experiment with drawing the recursive
curves described above. The Application creates a Frame in which the drawing occurs, and allows
you to input certain parameters:
the initial turtle attributes:
x start – the initial x coordinate of the turtle
y start – the initial y coordinate of the turtle
direction – the initial direction that the turtle is facing (a value of 0 corresponds to a
horizontal direction facing to the right)
the curve type itself:
Curve – the type of the curve
and the actual curve parameters:
ORDER – the order of the curve (be careful not to input values that are too large)
SIZE – the size of the curve (the straight distance from the start point to the end point of the
curve – this can be used to “zoom” in or out of a curve as well)
A screen shot of the application running (displaying an order 14 dragon curve) is shown below:
To run this program, download the Curves.jar file and double-click it.
──────────────────────────────
Page 20 of 20
© Copyright 2026 Paperzz