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! = 123…n for n1 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.