M180: Data Structures & Algorithms in Java Recursion Arab Open University 1 Recursive thinking • Recursion: a programming technique in which a method can call itself to solve a problem • recursive definition: one which uses the concept being defined in the definition itself – In some situations, a recursive definition can be an appropriate or elegant way to express a concept. • Before applying recursion to programming, it is best to practice thinking recursively 2 A recursive algorithm • Consider the task of finding out what place you are in a long line of people. – If you cannot see the front of the line, you could ask the person in front of you. – To answer your question, this person could ask the person in front of him/her, and so on. 3 A recursive algorithm • Once the front person answers their place in line (first), this information is handed back, one person at a time, until it reaches you. – This is the essence of recursive algorithms; many invocations of the same method each solve a small part of a large problem. 4 Infinite Recursion • All recursive definitions have to have a non-recursive part • If they didn't, there would be no way to terminate the recursive path • Such a definition would cause infinite recursion • This problem is similar to an infinite loop, but the nonterminating "loop" is part of the definition itself • The non-recursive part is often called the base case Recursion – Java's looping constructs make implementing this process easy. sum(1) = 1 sum(N) = N + sum(N - 1) if N > 1 – Consider what happens when the definition is applied to the problem of calculating sum(4): sum(4) = 4 + sum(3) = 4 + 3 + sum(2) = 4 + 3 + 2 + sum(1) =4+3+2+1 – The fact that sum(1) is defined to be 1 without making reference to further invocations of sum saves the process from going on forever and the definition from being circular. Factorial example • The factorial for any positive integer N, written N!, is defined to be the product of all integers between 1 and N inclusive n! n (n 1) (n 2) ...1 // not recursive public static long factorial(int n) { long product = 1; for (int i = 1; i <= n; i++) { product *= i; } return product;} 7 Recursive factorial • factorial can also be defined recursively: n 1 n f (n 1) f ( n) n 0 1 • A factorial is defined in terms of another factorial until the basic case of 0! is reached // recursive public static long factorial(int n) { if (n == 0) { return 1; } else { return n * factorial(n - 1); }} 8 Recursive factorial 5! 120 5 * 4! 24 4 * 3! 6 3 * 2! 2 2 * 1! 1 Recursion vs. iteration • every recursive solution has a corresponding iterative solution – For example, N ! can be calculated with a loop • For some problems, recursive solutions are often more simple and elegant than iterative solutions • You must be able to determine when recursion is appropriate 10 Recursive Programming • Consider the problem of computing the sum of all the numbers between 1 and any positive integer N • This problem can be recursively defined as: N i N i 1 N 1 i N N 1 i 1 N N 1 N 2 N 3 i i 1 N 2 i i 1 Recursive Programming // This method returns the sum of 1 to num public int sum (int num) { int result; if (num == 1) result = 1; else result = num + sum (n-1); return result; } Recursive Programming result = 6 main sum(3) sum result = 3 sum(2) sum result = 1 sum(1) sum Fibonacci Recursion – Consider the definition of Fibonacci numbers below. – The first and second numbers in the Fibonacci sequence are 1. – Thereafter, each number is the sum of its two immediate predecessors, as follows: 1 1 2 3 5 8 13 21 34 55 89 144 233 ... Or in other words: fibonacci(1) = 1 fibonacci(2) = 1 fibonacci(N) = fibonacci(N - 1) + fibonacci(N - 2) if N > 2 – This is a recursive definition, and it is hard to imagine how one could express it no recursively. Fibonacci Recursion – As a second example of recursion, below is a method that calculates Fibonacci numbers: int fibonacci (int n){ if (n <= 2) return 1; else return fibonacci (n - 1) + fibonacci (n - 2); } Malformed Recursive Method – Here is a subtler example of a malformed recursive method: int badMethod (int n){ if (n == 1) return 1; else return n * badMethod(n - 2); } – This method works fine if n is odd, but when n is even, the method passes through the stopping state and keeps on going. Guidelines for Writing Recursive Methods – A recursive method must have a well-defined termination or stopping state. – For the factorial method, this was expressed in the lines: if (n == 1) return 1; – The recursive step, in which the method calls itself, must eventually lead to the stopping state. – For the factorial method, the recursive step was expressed in the lines: else return n * factorial(n - 1); Guidelines for Writing Recursive Methods – Because each invocation of the factorial method is passed a smaller value, eventually the stopping state must be reached. – Had we accidentally written: else return n * factorial(n + 1); – the method would describe an infinite recursion. – Eventually, the user would notice and terminate the program, or else the Java interpreter would run out memory, and the program would terminate with a stack overflow error. Number of Zeros in a Number • Example: 2030 has 2 zeros recursive • If n has two or more digits – the number of zeros is the number of zeros in n with the last digit removed – plus an additional 1 if the last digit is zero • Examples: – number of zeros in 20030 is number of zeros in 2003 plus 1 – number of zeros in 20031 is number of zeros in 2003 plus 0 numberOfZeros Recursive Design • numberOfZeros in the number N • K = number of digits in N • Decomposition: – numberOfZeros in the first K - 1 digits – Last digit • Composition: – Add: • numberOfZeros in the first K – 1 digits • 1 if the last digit is zero • Base case: – N has one digit (K = 1) numberOfZeros Method public static int numberOfZeros(int n) { int zeroCount; if (n==0) zeroCount = 1; else if (n < 10) zeroCount = 0; else if (n%10 == 0) zeroCount = numberOfZeros(n/10) + 1; else // n%10 != 0 zeroCount = numberOfZeros(n/10); return zeroCount; } Which is (are) the base case(s)? Why? Decompostion , Why? Composition, why? Execution Trace (decomposition) Each method invocation will execute one of the if-else cases shown at right. public static int numberOfZeros(int n) { int zeroCount; if (n==0) zeroCount = 1; else if (n < 10) zeroCount = 0; else if (n%10 == 0) zeroCount = numberOfZeros(n/10) + 1; else // n%10 != 0 zeroCount = numberOfZeros(n/10); return zeroCount; } numberOfZeros(2005) numberOfZeros(200) numberOfZeros(20) numberOfZeros(2) 5 0 0 Execution Trace (composition) Recursive calls return public static int numberOfZeros(int n) { int zeroCount; if (n==0) zeroCount = 1; else if (n < 10) zeroCount = 0; else if (n%10 == 0) zeroCount = numberOfZeros(n/10) + 1; else // n%10 != 0 zeroCount = numberOfZeros(n/10); return zeroCount; } numberOfZeros(2005)->2 + numberOfZeros(200)->2 + numberOfZeros(20)->1 numberOfZeros(2)->0 + 0->1 0->1 5->0