Joseph Haugh
University of New Mexico
IntList recursivelyIntLists recursivelyint sum1ToN(int n) {
int result = 0;
for (int i = 1; i <= n; i++) {
result += i;
}
return result;
}
The Fibonacci sequence is a famous sequence of numbers:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...Each number is the sum of the two numbers before it
But what are the first two numbers? They are just given: 0 and 1
Our goal: write a function fib(n) that returns the n-th Fibonacci number
We can define fib precisely:
fib(0) = 0
fib(1) = 1
fib(n) = fib(n-1) + fib(n-2) for n >= 2The first two lines are the starting values: we just know fib(0) and fib(1)
The third line says: to find fib(n), look at two smaller problems
The definition refers to itself, which is exactly what recursion means
To compute fib(5), we need fib(4) and fib(3); to compute fib(4) we need fib(3) and fib(2); and so on down to the starting values
| Definition | Java |
|---|---|
fib(0) = 0 |
if (n == 0) return 0; |
fib(1) = 1 |
if (n == 1) return 1; |
fib(n) = fib(n-1) + fib(n-2) |
return fib(n-1) + fib(n-2); |
int fib(int n) {
}
int fib(int n) {
if (n == 0) return 0; // base case
if (n == 1) return 1; // base case
}
int fib(int n) {
if (n == 0) return 0; // base case
if (n == 1) return 1; // base case
return fib(n - 1) + fib(n - 2); // recursive case
}
fib calls itself with a smaller inputfib(n-1) and fib(n-2) will give the right answersvoid main() {
IO.println(fib(0)); // 0
IO.println(fib(1)); // 1
IO.println(fib(5)); // 5
IO.println(fib(7)); // 13
}
fib(0) = 0
fib(1) = 1
fib(n) = fib(n-1) + fib(n-2)
fib(4)
= fib(3) + fib(2)
= (fib(2) + fib(1)) + fib(2)
= ((fib(1) + fib(0)) + fib(1)) + fib(2)
= ((1 + 0) + 1) + fib(2)
= 2 + (fib(1) + fib(0))
= 2 + (1 + 0)
= 3
n == 0 or n == 1)int fib(int n) {
return fib(n - 1) + fib(n - 2); // no base cases!
}
fib(4) calls fib(3), which calls fib(2), which calls fib(1), which calls fib(0), which calls fib(-1), …IntList: a resizable list of int values| Method | What it does |
|---|---|
add(x) |
Appends x to the end |
get(i) |
Returns the element at index i |
pop() |
Removes and returns the last element |
size() |
Returns the number of elements |
isEmpty() |
Returns true if the list has no elements |
head() and tail()head() returns the first element of the list
head() returns 3tail() returns a new list with everything except the first element
tail() returns [7, 2, 5]public class IntList {
// fields, constructor, add, get, pop, size, isEmpty ...
public int head() {
if (isEmpty()) {
throw new IllegalStateException("List is empty");
}
return get(0);
}
}
head() is just get(0) with an empty-list checkpublic class IntList {
// fields, constructor, add, get, pop, size, isEmpty, head ...
public IntList tail() {
if (isEmpty()) {
throw new IllegalStateException("List is empty");
}
IntList result = new IntList();
for (int i = 1; i < size(); i++) {
result.add(get(i));
}
return result;
}
}
tail() builds a fresh IntList containing every element after index 0Now let’s write a function that sums all the elements:
IntList scores = new IntList();
scores.add(3);
scores.add(7);
scores.add(2);
scores.add(5);
IO.println(sum(scores)); // 17How do we think about this recursively?
sum(list) = list.head() + sum(list.tail())int sum(IntList list) {
}
int sum(IntList list) {
if (list.isEmpty()) {
return 0; // base case: nothing left to add
}
}
int sum(IntList list) {
if (list.isEmpty()) {
return 0; // base case: nothing left to add
}
return list.head() + sum(list.tail()); // recursive case
}
list.head() is the first elementlist.tail() is the rest of the list, one element shorterint sum(IntList list) {
if (list.isEmpty()) {
return 0;
}
return list.head() + sum(list.tail());
}
sum([3, 7, 2, 5])
= 3 + sum([7, 2, 5])
= 3 + 7 + sum([2, 5])
= 3 + 7 + 2 + sum([5])
= 3 + 7 + 2 + 5 + sum([])
= 3 + 7 + 2 + 5 + 0 // base case
= 17
void main() {
IntList scores = new IntList();
scores.add(3);
scores.add(7);
scores.add(2);
scores.add(5);
IO.println(sum(scores)); // 17
}
IntList a = new IntList(); // [1, 2, 3]
a.add(1); a.add(2); a.add(3);
IntList b = new IntList(); // [4, 5]
b.add(4); b.add(5);
IntList c = append(a, b);
// c is [1, 2, 3, 4, 5]
a, then all elements of b, into a new listcopyFrom(src, i, dest)
src starting at index i into desti == src.size(), nothing left to copy, just returnsrc.get(i) to dest, then copy the rest starting at i + 1void copyFrom(IntList src, int i, IntList dest) {
}
void copyFrom(IntList src, int i, IntList dest) {
if (i == src.size()) {
return; // base case: nothing left to copy
}
}
void copyFrom(IntList src, int i, IntList dest) {
if (i == src.size()) {
return; // base case: nothing left to copy
}
dest.add(src.get(i)); // copy the current element
copyFrom(src, i + 1, dest); // recursive case: copy the rest
}
i + 1i reaches src.size(), the copying is doneIntList append(IntList a, IntList b) {
IntList result = new IntList();
copyFrom(a, 0, result); // copy all of a
copyFrom(b, 0, result); // then all of b
return result;
}
append creates a fresh IntList to hold the combined resultcopyFrom twice: once for a, once for bcopyFrom; append just coordinatesvoid copyFrom(IntList src, int i,
IntList dest) {
if (i == src.size()) {
return;
}
dest.add(src.get(i));
copyFrom(src, i + 1, dest);
}
copyFrom(a, 0, result)
-> dest.add(1); copyFrom(a, 1, result)
-> dest.add(2); copyFrom(a, 2, result)
-> dest.add(3); copyFrom(a, 3, result)
-> return // base case
// result is now [1, 2, 3]
copyFrom(b, 0, result)
-> dest.add(4); copyFrom(b, 1, result)
-> dest.add(5); copyFrom(b, 2, result)
-> return // base case
// result is now [1, 2, 3, 4, 5]
Recursion
Common mistake
Three Examples
| Function | Base case | Recursive case |
|---|---|---|
fib(n) |
n == 0 or 1 | fib(n-1) + fib(n-2) |
sum(list) |
list.isEmpty() | head() + sum(tail()) |
copyFrom(src, i, dest) |
i == size | add get(i), recurse i+1 |