Uni11 PDF
Uni11 PDF
Uni11 PDF
Recursion
Summary
• Inductively defined domains
• Recursion and recursive methods
• Run-time memory management
• Multiple recursion
The recursive definition of a function reflects the structure of the inductive definition of the domain on which
the function operates; hence we have:
1
2 UNIT 11
• one (or more) base cases, for which the result of the function can be determined directly;
• one (or more) recursive cases, for which the computation of the result is reduced to the computation of
the same function on a smaller/simpler value of the domain.
The fact that the domain on which the function operates is defined inductively guarantees us that, by repeatedly
applying the recursive cases, we will reach in a finite number of steps one of the base cases.
Starting from the recursive definition of a function, we can usually provide rather easily an implementation by
means of a recursive method.
Example: Implementation of the factorial by means of a recursive method:
public static long factorial(long n) {
if (n == 0)
return 1;
else
return n * factorial(n-1);
}
Implementation:
public static int sum(int x, int y) {
if (y == 0)
return x;
else
return 1 + sum(x, y-1);
}
Implementation (we assume that the method sum() is defined in the same class):
public static int product(int x, int y) {
if (y == 0)
return 0;
else
return sum(x, product(x, y-1));
}
Implementation (we assume that the method product() is defined in the same class):
Diego
c Calvanese Lecture Notes for Introduction to Programming A.A. 2006/07
Recursion 3
fact(n) = n · (n − 1) · (n − 2) · · · · · 2 · 1
Diego
c Calvanese Lecture Notes for Introduction to Programming A.A. 2006/07
4 UNIT 11
• otherwise (i.e., if the first character of s is different from c), return the number of occurrences of c in the
string equal to s without the first character.
Implementation:
public static int countChars(String s, char c) {
if (s.length() == 0)
return 0;
else if (s.charAt(0) == c)
return 1 + countChars(s.substring(1), c);
else
return countChars(s.substring(1), c);
}
Implementation: we access the file through a BufferedReader, and we assume that each integer is written on
a separate line.
public static int maximum(BufferedReader br) throws IOException {
String s = br.readLine();
if (s == null)
return 0;
else {
int i = Integer.parseInt(s);
int m = maximum(br);
return (m > i)? m : i;
}
}
Diego
c Calvanese Lecture Notes for Introduction to Programming A.A. 2006/07
Recursion 5
Diego
c Calvanese Lecture Notes for Introduction to Programming A.A. 2006/07
6 UNIT 11
return 0;
else
return 1 + countLines(br);
}
Diego
c Calvanese Lecture Notes for Introduction to Programming A.A. 2006/07
Recursion 7
Diego
c Calvanese Lecture Notes for Introduction to Programming A.A. 2006/07
8 UNIT 11
Diego
c Calvanese Lecture Notes for Introduction to Programming A.A. 2006/07
Recursion 9
be the same, and the actual parameters must correspond in number and types to the formal parameters):
in our case, the method to execute must have the signature A(int).
3. The execution of the calling method is suspended : in our case, it is the main() method.
4. The AR is created for the current activation of the called method: in our case, the AR for the current
activation of A() is created; the AR contains:
• the memory locations for the formal parameters: in our case, the parameter pa of type int;
• the memory locations for the local variables: in our case, the parameter va of type int;
• a memory location for the return value: in our case indicated with RV;
• a memory location for the return address: in our case indicated with RA.
5. The values of the actual parameters are assigned to the corresponding formal parameters: in our case, the
formal parameter pa is initialized to the value 22.
6. The return address in the AR is set to the address of the next statement in the calling method that must be
executed at the end of the current invocation: in our case, the return address in the AR for the activation
of A() is set to the value 104, which is the address of the statement m4 of main(), to be executed when
the activation of A() will be finished; the AR at this point is as shown in 2 in the above figure.
7. The address of the first statement of the invoked method is assigned to the program counter : in our case,
the address 200, which is the address of the first statement of A(), is assigned to the program counter.
8. The next statement indicated by the program counter is executed (this is the first statement of the invoked
method): in our case, the statement at address 200, i.e., the first statement of A().
After these steps, the statements of the called method, in our case of A(), are executed in sequence. Specifically,
if the method contains itself method calls, such methods will be activated and executed, and will terminate. In
our case, the method B() will be activate and, executed, and will terminate, with a mechanisms analogous to
that for method A(); the stack of ARs evolves as shown above in 3 and 4.
Let us now analyze in detail what happens when the activation of A() terminates, i.e., when the statement
return va+pa; is executed. Before the execution of this statement, the stack of ARs is as shown in 4 in the
figure above (to be precise, the memory location reserved for the return value, indicated with RV in the figure,
is initialized the moment the return statement is executed, and not before).
1. The value stored in the memory location reserved for the return address in the current AR is assigned to
the program counter : in our case, such a value is equal to 104, which is precisely the address, stored in
AR, of the next statement in main() that should be executed.
2. If the called method needs to return a value, such a value is stored in a specific memory location in the
current AR: in our case, the value 67, which is the result of the evaluation of the expression va+pa, is
stored in the memory location indicated with RV, which is suited to contain the return value.
3. The AR for the current activation is removed from the stack of ARs, and the current AR becomes the
one immediately below it in the stack; together with the elimination of the AR from the stack of ARs, the
return value, if present, is copied into a memory location of the AR of the calling method : in our case,
the AR for the activation of A() is removed from the stack, and the current AR becomes the one for
the activation of main(); moreover, the value 67, stored in the memory location RV, is assigned to the
variable vm in the AR of main(); the stack of ARs is as shown in 5 in the figure above.
4. The next statement indicated by the program counter is executed (i.e., the statement specified in step 1):
in our case, the statement with address 104 is executed, and this corresponds to continuing the execution
of main().
Diego
c Calvanese Lecture Notes for Introduction to Programming A.A. 2006/07
10 UNIT 11
else {
System.out.println(" - Activation of recursive("
+ (i-1) + ")");
recursive(i-1);
System.out.print("Again in recursive(" + i + ")");
System.out.println(" - Finished");
}
return;
}
Diego
c Calvanese Lecture Notes for Introduction to Programming A.A. 2006/07
Recursion 11
11.19 Example: the last ones will be the first ones (cont’d)
Let us consider again the example in which we read the lines of a file (which we access through a
BufferedReader) and copy them to an output stream, inverting the order of the lines in the file.
For simplicity, we repeat here the recursive implementation we have seen before:
public static void copyInverse(BufferedReader br, PrintStream p) throws IOException {
String s = br.readLine();
if (s != null) {
copyInverse(br, p);
p.println(s);
}
}
At this point it is clear that the successive lines of the file are stored in the strings that we can access through
successive occurrences of the variable s in the ARs of the successive recursive calls of copyInverse(). Hence,
the stack of ARs is used as a temporary “data structure” in which to store the lines of the file before printing
them.
To implement this method in an iterative way, we would have to read all lines of the file and store them, before
we could start printing. Since we could not use the stack of ARs, we would need an additional data structure,
e.g., an array of strings.
Diego
c Calvanese Lecture Notes for Introduction to Programming A.A. 2006/07
12 UNIT 11
F (0), F (1), F (2), . . . is called the sequence of Fibonacci numbers, and it starts with:
0, 1, 1, 2, 3, 5, 8, 13, 21, . . .
We implement a recursive method that takes a positive integer n as parameter and returns the n-th Fibonacci
number.
public static long fibonacci(long n) {
if (n < 0) return -1; // F(n) is not defined when n is negative
if (n == 0)
return 0;
else if (n == 1)
return 1;
else
return fibonacci(n-1) + fibonacci(n-2);
}
Diego
c Calvanese Lecture Notes for Introduction to Programming A.A. 2006/07
Recursion 13
– all disks (except the one to be moved) have to be on one of the three supports;
– it is possible to move only one disk at a time, taking it from the top of the tower on one of the
supports and placing it on the top of the tower on another support;
– a disk can never be placed on a smaller disk.
The initial state (a), an intermediate state (b), and the final state (c) for a set of 6 disks are shown in the
following figures:
(a)
1 2 3
(b)
1 2 3
(c)
1 2 3
We want to realize a program that prints the sequence of moves to be done. For each move we want to print a
statement as follows (where x and y are either 1, 2, or 3):
move a disk from support x to support y
Idea: to move n > 1 disks from support 1 to support 2, using 3 as auxiliary support:
1. move n − 1 disks from 1 to 3 (without moving the n-th disk)
2. move the l’n-th disk from 1 to 2
3. move n − 1 disk from 3 to 2 (without moving the n-th disk)
Implementation (this is another example of multiple recursion):
import javax.swing.JOptionPane;
private static void move(int n, int source, int dest, int aux) {
if (n == 1)
moveADisk(source, dest);
else {
move(n-1, source, aux, dest);
moveADisk(source, dest);
move(n-1, aux, dest, source);
}
}
Diego
c Calvanese Lecture Notes for Introduction to Programming A.A. 2006/07
14 UNIT 11
System.exit(0);
}
}
Even if we ignore “1 + ” in the case for n > 1, we have that act(n) = 2n−1 . Hence we have that act(n) > 2n−1 .
Note that, in the case of the problem of the Towers of Hanoi, the exponential number of activations (i.e., moves)
is an intrinsic characteristic of the problem, i.e., there is no better solution.
r−1
r hr, ci
r+1
In the following figure, the character ’*’ represents a land zone, while the character ’o’ represents a water
zone. Moor 1 has no traversal, while moor 2 has a traversal (shown in the figure).
0 1 2 3 4 5 0 1 2 3 4 5
0 * o o * o o 0 * o o * o o
1 o * o o o o 1 * o o o o o
2 o o * * o o 2 o * o o o *
3 * * o o o o 3 o o * * * o
4 * * * o * * 4 o * o o o o
Moor 1 Moor 2
We want to check the existence of at least one traversal, and print it out, if it exists (if there is more than one
traversal, it is sufficient to print out the first one that we find).
Diego
c Calvanese Lecture Notes for Introduction to Programming A.A. 2006/07
Recursion 15
• construction of a random moor, given the number of rows, the number of columns, and a real value in the
range [0..1] representing the probability that a generic zone is a zone of land;
• return of the number of rows;
• return of the number of columns;
• check if the zone with coordinates hr, ci is of land.
Moreover, in the class we override the toString() method of Object in such a way that it prints a moor by
using the ’*’ character for a land zone, and the ’o’ character for a water zone.
In realizing the class, we choose to represent the moor by means of a matrix of boolean, in which the value
true represents a land zone, and the value false represents a water zone.
public class Moor {
Diego
c Calvanese Lecture Notes for Introduction to Programming A.A. 2006/07
16 UNIT 11
namely hr − 1, c + 1i, hr, c + 1i, and hr + 1, c + 1i. Instead, if the zone is a water zone, we cannot go on and the
search from that zone terminates. The overall search terminates with success when we arrive on a zone on the
last column (i.e., c is equal to C − 1) and such a zone is of land.
The generic search step can be implemented through the following recursive method searchPath(), that takes
as parameters a moor and the coordinates hr, ci of the zone from where to start searching the path through the
moor.
private static boolean searchPath(Moor m, int r, int c) {
if (the coordinates <r,c> of m are not valid ||
in m <r,c> is a water zone )
return false;
else if (<r,c> is on the right border of m)
return true;
else
return searchPath(m, r-1, c+1) ||
searchPath(m, r , c+1) ||
searchPath(m, r+1, c+1);
}
The method searchPath() checks only if there is a path from a generic position hr, ci to the last column. Since
a path could start from an arbitrary position on the first column, we have to repeatedly call this method on
the positions hr, 0i of the first column, until we have found a traversal or we have searched without success by
starting in the first column of all rows up to the last one. This is done by a method traverseMoor().
public Traversal(Moor m) {
moor = m;
traversal = new int[moor.getNumColumns()];
found = traverseMoor(moor, traversal);
}
Diego
c Calvanese Lecture Notes for Introduction to Programming A.A. 2006/07
Recursion 17
// auxiliary methods
Exercises
Exercise 11.1. Provide an iterative implementation of a method that computes the n-th Fibonacci number.
Exercise 11.2. Modify the recursive implementation of the fibonacci() method in such a way that, when
it is called on the integer n, it computes besides the n-th Fibonacci number, also the total number of recursive
activations of fibonacci() used for the computation.
Diego
c Calvanese Lecture Notes for Introduction to Programming A.A. 2006/07
18 UNIT 11
Exercise 11.3. Provide an implementation of the method that calculates the Ackermann function A(m, n),
which is defined as follows:
n + 1, if m = 0 (base case)
A(m, n) = A(m − 1, 1), if n = 0 (recursive case)
A(m − 1, A(m, n − 1)), otherwise (recursive case)
Note that the Ackermann function grows very fast (it is a non-elementary function): A(x, x) grows faster than
···2x
any tower of exponentials 22 .
Exercise 11.4. Implement recursive methods that are based on the following inductive definitions:
• greatest common divisor:
x, if y = 0
gcd (x, y) =
gcd (y, r), ify > 0 and x = q × y + r, with 0 ≤ r < y
Exercise 11.5. Provide the implementation of a recursive method that counts how many occurrences of 1
appear in a sequence of integers read from a file (accessed through a BufferedReader).
Exercise 11.6. Provide the implementation of a recursive method that takes as parameters a string s and a
character c and returns the length of the longest sequence of consecutive occurrences of character c in s.
Exercise 11.7. Provide the implementation of the method toString() of the class Traversal in such a way
that the returned string represents the whole moor, using:
• the character ’o’ for the water zones,
• the character ’#’ for the land zones that belong to the traversal, and
• the character ’*’ for the remaining land zones.
Exercise 11.8. In the implementation of the search of a traversal through a moor shown above, certain
land zones could be visited several times. This makes the search for a traversal inefficient, in the worst case
even exponential in the number of columns of the moor. Provide examples of a moor with a traversal and of
a moor without a traversal for which the method searchTraversal(), as implemented above, is activated a
number of times that is exponential in the number of columns of the moor. Modify the class Traversal and
the implementation of the method searchTraversal() in such a way that zones of the moor that have already
been visited are marked, and thus are not visited several times. Verify that, with the new implementation,
the number of activations of searchTraversal() is proportional to (rather than exponential in) the number of
zones of the moor.
Diego
c Calvanese Lecture Notes for Introduction to Programming A.A. 2006/07
Recursion 19
Exercise 11.9. Modify the class Traversal in such a way that it returns a traversal through the moor in
the case where we can move in all directions (as long as we stay on land zones). Note that, in this case, it
is necessary to modify the representation of a traversal, since it could be longer than the number of columns
of the moor, and hence could traverse different zones on the same column. Moreover, in this case, it becomes
unavoidable to mark the land zones that have already been visited during the search for a traversal, in order to
avoid infinite looping on the same zones.
Diego
c Calvanese Lecture Notes for Introduction to Programming A.A. 2006/07