Recursion

advertisement
Recursion
CS 367 – Introduction to Data Structures
Definition
• Mathematically
– a recursive function is one that is defined in
terms of an absolute case and itself
– example
n=
1
n*(n-1)!
Consider n = 3:
n
0!
1!
2!
3!
n=0
n>0
result
1
1 * 0! = 1
2 * 1! = 2
3 * 2! = 6
Definition
• Programming
– a recursive function is one that knows the
answer to a specific case; in all other cases,
the function calls itself again
– example
int factorial(int n) {
if(n == 0)
return 1;
else
return n * factorial(n – 1);
}
Function Call
• To understand recursion, we need a better
idea of what happens on a function call
– remember the program stack?
• on a function call, the compiler creates an
activation record
–
–
–
–
parameters passed in
local variables
return address
return value (where applicable)
• activation record gets pushed on the program
stack
Activation Records
f3()
f2()
f1()
main()
parameters
local variables
return address
return value
parameters
local variables
return address
return value
parameters
local variables
return address
return value
Calling a Function
• The program compiler takes care of
building the activation record for each
function
– this means you don’t have to worry about
writing it
– it also means that a single function call is
really a lot more code than you think
• this means it takes awhile to do
Calling a Function
• High level code
void main() {
int z = double(5);
}
int double(int x) {
int y = x * 2;
return y;
}
• Compiler code
# main
ld r1, 5
push addr
push r1
jmp # double
# double
pop r2
mult r3, r2, 2
pop r2
push r3
jmp r2
Calling a Function
• The previous compiler code is very incomplete
(and not quite correct either )
• Previous code does prove a point
– calling a function requires more memory
• must create and store the activation record
– calling a function takes more time
• must run extra code to build the activation record
• Fastest program would be one that only had a
main function
– this is very unrealistic – why?
– if it were easy to do, still would be a bad idea – why?
Anatomy of a Recursive Call
• Consider the factorial example used
earlier
int factorial(int n) {
if(n == 0) { return 1; }
else { return n * factorial(n-1); }
}
• Now consider the following code for main()
void main() {
int f = factorial(3);
print(f);
}
Anatomy of a Recursive Call
factorial(3)
factorial(2)
n=1
f3() ret = 1 * f4
n=2
f2() ret = 2 * f3
n=3
f1() ret = 3 * f2
n=2
f2() ret = 2 * f3
n=3
f1() ret = 3 * f2
n=3
f1() ret = 3 * f2
main() f =
factorial(1)
main() f =
main() f =
main() f =
return
return
return
factorial(0)
f3()
f3()
f2()
f1()
n=0
ret = 1
n=1
ret = 1 *
n=2
ret = 2 *
n=3
ret = 3 *
main() f =
n=1
f3() ret = 1 * 1
n=2
f2() ret = 2 * f3
n=3
f1() ret = 3 * f2
main() f =
n=2
f2() ret = 2 * 1
n=3
f1() ret = 3 * f2
main() f =
n=3
f1() ret = 3 * 2
main() f =
Anatomy of a Recursive Call
• Obviously, the last step in the previous
example is missing (not enough room)
– f1 will return 6 and the value (f) in main will be
equal to 6
• Major point
– using recursion can get very expensive
• consume lots of memory
• execute a lot of extra instructions
– what is the alternative?
Loops
• Almost any recursive function can be rewritten as a loop
– a loop will be much less time consuming
• no extra activation records to construct and store
– consider the factorial example one last time
int factorial(int n) {
int sum = 1;
for(int i=1; i<=n; i++)
sum *= i;
return sum;
}
Loops
• Notice no function calls inside this version
of factorial()
– will run much faster than the recursive version
• Why use recursion?
– in some cases it is easier to understand and
write a recursive function
Reverse()
• Consider a function that prints a string of
characters in the reverse order
– ABC will be printed out as CBA
void reverse() {
char ch = getChar();
if(ch != ‘\n’) {
reverse();
System.out.println(ch);
}
}
Reverse()
• Note that this recursive function doesn’t
return anything
– simply reads a character and waits to print it
until the one after it is printed
• very easy to understand
• very quick to program
– how would you convert this to an iterative
solution?
Reverse()
• Sol’n in Java
• Sol’n in C/C++
void reverse() {
String stack = new String();
stack = buffer.readLine();
for(top=stack.length()-1; top>=0; top--)
System.out.print(stack.charAt(top));
}
void reverse() {
char stack[80];
int top=0;
stack[top] = getch();
while(stack[top] != ‘\n’)
stack[++top] = getch();
for(top -= 1; top >= 0; top--)
System.out.print(stack[top]);
}
Reverse()
• Iterative sol’n must first read whole string into
memory
– then it can use a loop to print the string out
– Java sol’n looks so simple because of the
String.length() operator
• it tells us exactly how long the string is
• many languages don’t have this feature
• There are fewer steps and less code in the
recursive sol’n
– because we’re interacting with I/O, recursive nature
isn’t such a big deal
• why?
Backtracing
• One of the best uses of recursion is in navigating
a large, directed search space
– in other words, if you are at a certain point, P, in the
search space, you need to pick the next spot to
search
• if you go in one direction, you can only go back the other
direction by first returning to point P
– examples
•
•
•
•
finding a path from one city to another
8 queens example
best move in a game
searching through a tree data structure (next week)
Finding a Path
• Consider the graph of flights from the
lecture on stacks
Z
Key
: city (represented as C)
Y
C1
W
R
C2 : flight from city C to city C
1
2
S
P
T
Example
flight goes from W to S
W
X
Q
S
Finding a Path
• We showed how to use a stack to find a route
from P to Y
• We can also do it using recursion
public boolean findPath(City origin, City destination) {
// pick a possible city to go to – make that city the origin
if(findPath(nextCity, destination))
// found the city
else
// that path didn’t work, try another one
}
• The above recursive sol’n is still going to need
some kind of loop inside it
– have to check all the possible routes from a city
Download