0% found this document useful (0 votes)
63 views

Recursion

Recursion explained

Uploaded by

Cociorba Andrei
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
63 views

Recursion

Recursion explained

Uploaded by

Cociorba Andrei
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 23

Recursion and

Recursive Backtracking

Computer Science E-119


Harvard Extension School
Fall 2012
David G. Sullivan, Ph.D.

Iteration
When we encounter a problem that requires repetition,
we often use iteration i.e., some type of loop.
Sample problem: printing the series of integers from
n1 to n2, where n1 <= n2.
example: printSeries(5, 10) should print the following:
5, 6, 7, 8, 9, 10

Here's an iterative solution to this problem:


public static void printSeries(int n1, int n2) {
for (int i = n1; i < n2; i++) {
System.out.print(i + ", ");
}
System.out.println(n2);
}

Recursion
An alternative approach to problems that require repetition
is to solve them using recursion.
A recursive method is a method that calls itself.
Applying this approach to the print-series problem gives:
public static void printSeries(int n1, int n2) {
if (n1 == n2) {
System.out.println(n2);
} else {
System.out.print(n1 + ", ");
printSeries(n1 + 1, n2);
}
}

Tracing a Recursive Method


public static void printSeries(int n1, int n2) {
if (n1 == n2) {
System.out.println(n2);
} else {
System.out.print(n1 + ", ");
printSeries(n1 + 1, n2);
}
}

What happens when we execute printSeries(5, 7)?


printSeries(5, 7):
System.out.print(5 + ", ");
printSeries(6, 7):
System.out.print(6 + ", ");
printSeries(7, 7):
System.out.print(7);
return
return
return

Recursive Problem-Solving
When we use recursion, we solve a problem by reducing it
to a simpler problem of the same kind.
We keep doing this until we reach a problem that is
simple enough to be solved directly.
This simplest problem is known as the base case.
public static void printSeries(int n1, int n2) {
if (n1 == n2) {
// base case
System.out.println(n2);
} else {
System.out.print(n1 + ", ");
printSeries(n1 + 1, n2);
}
}

The base case stops the recursion, because it doesn't


make another call to the method.

Recursive Problem-Solving (cont.)


If the base case hasn't been reached, we execute the
recursive case.
public static void printSeries(int n1, int n2) {
if (n1 == n2) {
// base case
System.out.println(n2);
} else {
// recursive case
System.out.print(n1 + ", ");
printSeries(n1 + 1, n2);
}
}

The recursive case:


reduces the overall problem to one or more simpler problems
of the same kind
makes recursive calls to solve the simpler problems

Structure of a Recursive Method


recursiveMethod(parameters) {
if (stopping condition) {
// handle the base case
} else {
// recursive case:
// possibly do something here
recursiveMethod(modified parameters);
// possibly do something here
}
}

There can be multiple base cases and recursive cases.


When we make the recursive call, we typically use parameters
that bring us closer to a base case.

Tracing a Recursive Method: Second Example


public static void mystery(int i) {
if (i <= 0) {
// base case
return;
}

// recursive case
System.out.println(i);
mystery(i 1);
System.out.println(i);
}

What happens when we execute mystery(2)?

Printing a File to the Console


Here's a method that prints a file using iteration:
public static void print(Scanner input) {
while (input.hasNextLine()) {
System.out.println(input.nextLine());
}
}

Here's a method that uses recursion to do the same thing:


public static void printRecursive(Scanner input) {
// base case
if (!input.hasNextLine()) {
return;
}
// recursive case
System.out.println(input.nextLine());
printRecursive(input); // print the rest
}

Printing a File in Reverse Order


What if we want to print the lines of a file in reverse order?
It's not easy to do this using iteration. Why not?
It's easy to do it using recursion!
How could we modify our previous method to make it
print the lines in reverse order?
public static void printRecursive(Scanner input) {
if (!input.hasNextLine()) {
// base case
return;
}
String line = input.nextLine();
System.out.println(line);
printRecursive(input); // print the rest
}

A Recursive Method That Returns a Value


Simple example: summing the integers from 1 to n
public static int sum(int n) {
if (n <= 0) {
return 0;
}
int total = n + sum(n 1);
return total;
}

Example of this approach to computing the sum:


sum(6) = 6 + sum(5)
= 6 + 5 + sum(4)

Tracing a Recursive Method


public static int sum(int n) {
if (n <= 0) {
return 0;
}
int total = n + sum(n 1);
return total;
}

What happens when we execute int x = sum(3);


from inside the main() method?
main() calls sum(3)
sum(3) calls sum(2)
sum(2) calls sum(1)
sum(1) calls sum(0)
sum(0) returns 0
sum(1) returns 1 + 0 or 1
sum(2) returns 2 + 1 or 3
sum(3) returns 3 + 3 or 6
main()

Tracing a Recursive Method on the Stack


public static int sum(int n) {
if (n <= 0) {
return 0;
}
int total = n + sum(n 1);
return total;
}
base case

Example: sum(3)

n
total

return 0

n
total

n
total

n
total

1
1

total = 1 + sum(0)
=1+ 0

return 1

n
total

n
total

n
total

n
total

n
total

n
total

n
total

n
total

n
total

n
total

2
3

n
total

return 3
n
total

time

Infinite Recursion
We have to ensure that a recursive method will eventually
reach a base case, regardless of the initial input.
Otherwise, we can get infinite recursion.
produces stack overflow there's no room for
more frames on the stack!
Example: here's a version of our sum() method that uses
a different test for the base case:
public static int sum(int n) {
if (n == 0) {
return 0;
}
int total = n + sum(n 1);
return total;
}

what values of n would cause infinite recursion?

3
6

Thinking Recursively
When solving a problem using recursion, ask yourself these
questions:
1. How can I break this problem down into one or more
smaller subproblems?
make recursive method calls to solve the subproblems

2. What are the base cases?


i.e., which subproblems are small enough to solve directly?

3. Do I need to combine the solutions to the subproblems?


If so, how should I do so?

Raising a Number to a Power


We want to write a recursive method to compute
xn = x*x*x**x
n of them

where x and n are both integers and n >= 0.


Examples:
210 = 2*2*2*2*2*2*2*2*2*2 = 1024
105 = 10*10*10*10*10 = 100000
Computing a power recursively: 210 = 2*29
= 2*(2 * 28)
=
Recursive definition:

xn = x * xn-1 when n > 0


x0 = 1

See ~cscie119/examples/recursion/Power.java

Power Method: First Try


public class Power {
public static int power1(int x, int n) {
if (n < 0)
throw new IllegalArgumentException(
n must be >= 0);
if (n == 0)
return 1;
else
return x * power1(x, n-1);
}
}
x5 n0
return 1

Example: power1(5,3)

x5 n3

x5 n1

x5 n1

x5 n1
return 5*1

x5 n2

x5 n2

x5 n2

x5 n2

x5 n3

x5 n3

x5 n3

x5 n3

x5 n2
return 5*5
x5 n3

x5 n3
return 5*25

time

Power Method: Second Try


Theres a better way to break these problems into subproblems.
For example: 210 = (2*2*2*2*2)*(2*2*2*2*2)
= (25) * (25) = (25)2
A more efficient recursive definition of xn (when n > 0):
xn = (xn/2)2 when n is even
xn = x * (xn/2)2 when n is odd (using integer division for n/2)
Let's write the corresponding method together:
public static int power2(int x, int n) {

Analyzing power2
How many method calls would it take to compute 21000 ?
power2(2, 1000)
power2(2, 500)
power2(2, 250)
power2(2, 125)
power2(2, 62)
power2(2, 31)
power2(2, 15)
power2(2, 7)
power2(2, 3)
power2(2, 1)
power2(2, 0)

Much more efficient than


power1() for large n.
It can be shown that
it takes approx. log2n
method calls.

An Inefficient Version of power2


What's wrong with the following version of power2()?
public static int power2Bad(int x, int n) {
// code to handle n < 0 goes here...
if (n == 0)
return 1;
if ((n % 2) == 0)
return power2(x, n/2) * power2(x, n/2);
else
return x * power2(x, n/2) * power2(x, n/2);
}

Processing a String Recursively


A string is a recursive data structure. It is either:
empty ("")
a single character, followed by a string
Thus, we can easily use recursion to process a string.
process one or two of the characters
make a recursive call to process the rest of the string
Example: print a string vertically, one character per line:
public static void printVertical(String str) {
if (str == null || str.equals("")) {
return;
}
System.out.println(str.charAt(0)); // first char
printVertical(str.substring(1));
// rest of string
}

Counting Occurrences of a Character in a String


Let's design a recursive method called numOccur().

numOccur(ch, str) should return the number of times that


the character ch appears in the string str

Thinking recursively:

Counting Occurrences of a Character in a String (cont.)


Put the method definition here:

Common Mistake
This version of the method does not work:
public static int numOccur(char ch, String str) {
if (str == null || str.equals("")) {
return 0;
}
int count = 0;
if (str.charAt(0) == ch) {
count++;
}
numOccur(ch, str.substring(1));
return count;
}

Another Faulty Approach


Some people make count "global" to fix the prior version:
public static int count = 0;
public static int numOccur(char ch, String str) {
if (str == null || str.equals("")) {
return 0;
}
if (str.charAt(0) == ch) {
count++;
}
numOccur(ch, str.substring(1));
return count;
}

Not recommended, and not allowed on the problem sets!


Problems with this approach?

Removing Vowels from a String


Let's design a recursive method called removeVowels().

removeVowels(str) should return a string in which all of the


vowels in the string str have been removed.

example:
removeVowels("recurse")

should return
"rcrs"

Thinking recursively:

Removing Vowels from a String (cont.)


Put the method definition here:

Recursive Backtracking: the n-Queens Problem


Find all possible ways of placing n queens on an n x n
chessboard so that no two queens occupy the same row,
column, or diagonal.
Sample solution
for n = 8:

Q
Q
Q
Q
Q
Q
Q
Q

This is a classic example of a problem that can be solved


using a technique called recursive backtracking.

Recursive Strategy for n-Queens


Consider one row at a time. Within the row, consider one
column at a time, looking for a safe column to place a queen.
If we find one, place the queen, and make a recursive call to
place a queen on the next row.
If we cant find one, backtrack by returning from the recursive
call, and try to find another safe column in the previous row.
Example for n = 4:
row 0: Q

col 0: safe

row 1:

Q
Q

col 0: same col

col 1: same diag

col 2: safe

4-Queens Example (cont.)


row 2:

col 0: same col

Q
Q

Q
Q

col 1: same diag col 2: same col/diag col 3: same diag

Weve run out of columns in row 2!


Backtrack to row 1 by returning from the recursive call.
pick up where we left off
we had already tried columns 0-2, so now we try column 3:
Q

Q
Q

we left off in col 2

try col 3: safe

Continue the recursion as before.

4-Queens Example (cont.)


row 2:

Q
Q

col 1: safe

col 0: same col

row 3:

Q
Q

Q
Q

Q
Q

Q
Q

col 0: same col/diag col 1: same col/diag col 2: same diag col 3: same col/diag

Backtrack to row 2:
Q

Q
Q

we left off in col 1 col 2: same diag

col 3: same col

Backtrack to row 1. No columns left, so backtrack to row 0!

4-Queens Example (cont.)


row 0:

row 1:

row 2:

Q
Q
Q

row 3:

Q
Q

Q
Q

Q
Q

Q
Q

A solution!

findSafeColumn() Method
public void findSafeColumn(int row) {
if (row == boardSize) { // base case: a solution!
solutionsFound++;
displayBoard();
if (solutionsFound >= solutionTarget)
System.exit(0);
return;
}
for (int col = 0; col < boardSize; col++) {
if (isSafe(row, col)) {
placeQueen(row, col);
// Move onto the next row.
findSafeColumn(row + 1);

Note: neither row++


nor ++row will work
here.

// If we get here, weve backtracked.


removeQueen(row, col);
}
}
}

(see ~cscie119/examples/recursion/Queens.java)

Tracing findSafeColumn()
public void findSafeColumn(int row) {
if (row == boardSize) {
// code to process a solution goes here...
}
for (int col = 0; col < BOARD_SIZE; col++) {
if (isSafe(row, col)) {
placeQueen(row, col);
findSafeColumn(row + 1);
removeQueen(row, col);
}
}
}
We can pick up
backtrack!
row: 2
col: 0,1,2,3

row: 0
col: 0

where we left off,


because the value
of col is stored in
the stack frame.

backtrack!
row: 3
col: 0,1,2,3

row: 2
col: 0,1

row: 2
col: 0,1

row: 1
col: 0,1,2

row: 1
col: 0,1,2

row: 1
col: 0,1,2

row: 1
col: 0,1,2,3

row: 1
col: 0,1,2,3

row: 1
col: 0,1,2,3

row: 0
col: 0

row: 0
col: 0

row: 0
col: 0

row: 0
col: 0

row: 0
col: 0

row: 0
col: 0

time

Template for Recursive Backtracking


void findSolutions(n, other params) {
if (found a solution) {
solutionsFound++;
displaySolution();
if (solutionsFound >= solutionTarget)
System.exit(0);
return;
}
for (val = first to last) {
if (isValid(val, n)) {
applyValue(val, n);
findSolutions(n + 1, other params);
removeValue(val, n);
}
}
}

Template for Finding a Single Solution


boolean findSolutions(n, other params) {
if (found a solution) {
displaySolution();
return true;
}
for (val = first to last) {
if (isValid(val, n)) {
applyValue(val, n);
if (findSolutions(n + 1, other params))

return true;
removeValue(val, n);
}
}

return false;
}

Data Structures for n-Queens


Three key operations:
isSafe(row, col): check to see if a position is safe
placeQueen(row, col)
removeQueen(row, col)

A two-dim. array of booleans would be sufficient:


public class Queens {
private boolean[][] queenOnSquare;

Advantage: easy to place or remove a queen:


public void placeQueen(int row, int col) {
queenOnSquare[row][col] = true;
}
public void removeQueen(int row, int col) {
queenOnSquare[row][col] = false;
}

Problem: isSafe() takes a lot of steps. What matters more?

Additional Data Structures for n-Queens


To facilitate isSafe(), add three arrays of booleans:
private boolean[] colEmpty;
private boolean[] upDiagEmpty;
private boolean[] downDiagEmpty;

An entry in one of these arrays is:


true if there are no queens in the column or diagonal
false otherwise

Numbering diagonals to get the indices into the arrays:


upDiag = row + col
downDiag =
(boardSize 1) + row col
0

Using the Additional Arrays


Placing and removing a queen now involve updating four
arrays instead of just one. For example:
public void placeQueen(int row, int col) {
queenOnSquare[row][col] = true;
colEmpty[col] = false;
upDiagEmpty[row + col] = false;
downDiagEmpty[(boardSize - 1) + row - col] = false;
}

However, checking if a square is safe is now more efficient:


public boolean isSafe(int row, int col) {
return (colEmpty[col]
&& upDiagEmpty[row + col]
&& downDiagEmpty[(boardSize - 1) + row - col]);
}

Recursive Backtracking II: Map Coloring


Using just four colors (e.g., red, orange, green, and blue), we
want color a map so that no two bordering states or countries
have the same color.
Sample map (numbers show alphabetical order in full list of
state names):

This is another example of a problem that can be solved using


recursive backtracking.

Applying the Template to Map Coloring


boolean findSolutions(n, other params) {
if (found a solution) {
displaySolution();
return true;
}
for (val = first to last) {
if (isValid(val, n)) {
applyValue(val, n);
if (findSolutions(n + 1, other params))
return true;
removeValue(val, n);
}
}
meaning in map coloring
template element
return false;
}

found a solution
val
isValid(val, n)
applyValue(val, n)
removeValue(val, n)

Map Coloring Example


consider the states in alphabetical order. colors = { red, yellow, green, blue }.

We color Colorado through


Utah without a problem.
Colorado:
Idaho:
Kansas:
Montana:
Nebraska:
North Dakota:
South Dakota:
Utah:

No color works for Wyoming,


so we backtrack

Map Coloring Example (cont.)

Now we can complete


the coloring:

Recursive Backtracking in General


Useful for constraint satisfaction problems that involve assigning
values to variables according to a set of constraints.
n-Queens:
variables = Queens position in each row
constraints = no two queens in same row, column, diagonal

map coloring
variables = each states color
constraints = no two bordering states with the same color

many others: factory scheduling, room scheduling, etc.


Backtracking reduces the # of possible value assignments that
we consider, because it never considers invalid assignments.
Using recursion allows us to easily handle an arbitrary
number of variables.
stores the state of each variable in a separate stack frame

Recursion vs. Iteration


Recursive methods can often be easily converted to a
non-recursive method that uses iteration.
This is especially true for methods in which:
there is only one recursive call
it comes at the end (tail) of the method
These are known as tail-recursive methods.
Example: an iterative sum() method.
public static int sum(n) {
// handle negative values of n here
int sum = 0;
for (int i = 1; i <= n; i++)
sum += i;
return sum;
}

Recursion vs. Iteration (cont.)


Once you're comfortable with using recursion, you'll find that
some algorithms are easier to implement using recursion.
We'll also see that some data structures lend themselves to
recursive algorithms.
Recursion is a bit more costly because of the overhead involved
in invoking a method.
Rule of thumb:
if it's easier to formulate a solution recursively, use recursion,
unless the cost of doing so is too high
otherwise, use iteration

You might also like