Recursion

advertisement
Recursion
Recursion is a problem solving strategy of splitting up a problem into
simpler similar subproblems, solving the subproblems, using the same
strategy, and finally combining the solutions of the subproblems to
obtain a solution of the original problem. If the problem is simple
enough then solve it immediately.
This can be expressed as an algorithm:
solve(problem):
if problem is simple then
return solution of problem
else
split problem into subproblem1,…,subproblemn
solution1  solve(subproblem1)
. . .
solutionn  solve(subproblemn)
combine solution1,…,solutionn into solution
return solution
Details about the definition of “simple”, how to split the problem into
subproblems, how many subproblems there are, and how to combine
the subproblem solutions depend on the actual problem and the
ingenuity of the designer. A recursive algorithm is implemented as a
function that calls itself.
Example: The factorial problem is often used to illustrate recursion.
The problem is to calculate n! = 123…n for n1 and 0!=1. The
recursive step is n! = n(n-1)! The recursive algorithm is
factorial(n):
if n=0 then
return 1
else
return n  factorial(n-1)
The factorial can also be computed iteratively by accumulating the
product of successive numbers. Both the recursive and iterative
algorithms require the same number of multiplications, however the
recursive algorithm is slower due to the compiler overhead of
implementing recursion using stacks.
Example: The Fibonacci sequence: 1 1 2 3 5 8 13 … starts off with 1 1,
and is continued by taking the sum of the previous two numbers.
The recursive algorithm is
fib(n):
if n  2 then
return 1
else
return fib(n-2)+fib(n-1)
The sequence can also be computed iteratively by starting with 1 1,
and repeatedly adding the last two numbers. The recursive algorithm
is exponentially inefficient because the same subproblem needs to be
computed many times. This can be avoided by storing subproblem
solutions; if the subproblem is already stored then its solution is
accessed immediately, otherwise the subproblem is solved recursively
and the solution is stored.
Example: Sorting by “merge sort”. The list is split into two halves,
each is sorted recursively, then the resulting sorted lists a re merged
together.
The recursive algorithm is
mergeSort(list):
if list has 1 item then
return list
else
split list into two (roughly equal) halfs: list1 list2
sortedList1  mergeSort(list1)
sortedList2  mergeSort(list2)
sortedList  merge sortedList1 and sortedList2
return sortedList
The merging requires parallel scanning of the two sorted lists, and
requires linear time. The number of comparisons is proportional to n
log n, where n is the size of the list.
Example: Consider this game between two players. A player is given a
number,n, and has the choice of giving either n-1 or n/2 to his
opponent player. If a player is given the number 1 then he wins.
Here is a recursive algorithm that determines if a number is a winning
position, assuming that the players are expert.
win(n):
if n = 1 then
return true
if lose(n-1) then
return true
if lose(n/2) then
return true
return false
lose(n):
if win(n) then
return false
else
return true
This can be simplified to:
win(n):
return n=1 or !win(n-1) or !win(n/2)
This also suffers from the inefficiency of recalculating problems many
times. As a challenge, find a very simple way of computing win(n), by
considering the binary representation of n.
References
1. http://www.cs.princeton.edu/introcs/23recursion/
Contains more examples such as Towers of Hanoi, recursive
graphics,…
2. http://cs.senecac.on.ca/~leung/btp500.html
See Notes 1 for details of how recursion works using stacks. Use
recursion to count the number of 1’s in the binary representation of a
number, then look at the discussion and solution. (Can you simplify
the solution by using only one call?) The “non-attacking queens”
problem is also discussed.
Download