Recursion Loops and Switches

- 161 - } final static int[] RETURNS = { 99, 55, -1, 6, 8, 12, 15, 29, 11111, 12345, 6666, 9876, 12 }; public static int switch4int i { equivalent to switch3 , but using an array lookup instead of a switch statement. if i 1 || i 13 return -1; else return RETURNS[i-1]; } }

7.4 Recursion

Recursive algorithms are used because theyre often clearer and more elegant than the alternatives, and therefore have a lower maintenance cost than the equivalent iterative algorithm. However, recursion often but not always has a cost to it; recursive algorithms are frequently slower. So it is useful to understand the costs associated with recursion, and how to improve the performance of recursive algorithms when necessary. Recursive code can be optimized by a clever compiler as is done with some C compilers, but only if presented in the right way typically, it needs to be tail-recursive: see Tail Recursion . For example, Jon Bentley [7] found that a functionally identical recursive method was optimized by a C compiler if he did not use the ?: conditional operator using if statements instead. However, it was not optimized if he did use the ?: conditional operator. He also found that recursion can be very expensive, taking up to 20 times longer for some operations that are naturally iterative. Bentleys article also looks briefly at optimizing partial match searching in ternary search trees by transforming a tail recursion in the search into an iteration. See Chapter 11 , for an example of tuning a ternary search tree, including an example of converting a recursive algorithm to an iterative one. [7] The Cost of Recursion, Dr. Dobbs Journal, June 1998. Tail Recursion A tail-recursive function is a recursive function for which each recursive call to itself is a reduction of the original call. A reduction is the situation where a problem is converted into a new problem that is simpler, and the solution of that new problem is exactly the solution of the original problem, with no further computation necessary. This is a subtle concept, best illustrated with a simple example. I will take the factorial example used in the text. The original recursive solution is: public static long factorial1int n { if n 2 return 1L; else return nfactorial1n-1; } This is not tail-recursive, since each call to itself does not provide the solution to the original problem. Instead, the recursive call provides a partial solution that must be - 162 - multiplied by a number to get the final result. If you consider the operating stack of the VM, each recursive call must be kept on the stack, as each call is incomplete until the next call above on the stack is returned. So factorial120 goes on the stack and stays there until factorial119 returns. factorial119 goes above factorial120 on the stack and stays there until factorial118 returns, etc. The tail-recursive version of this function requires two functions: one to set up the recursive call to keep compatibility, and the actual recursive call. This looks like: public static long factorial1aint n { NOT recursive. Sets up the tail-recursive call to factorial1b if n 2 return 1L; else return factorial1bn, 1L; } public static long factorial1bint n, long result { No need to consider n 2, as factorial1a handles that if n == 2 return 2Lresult; else return factorial1bn-1, resultn; } I have changed the recursive call to add an extra parameter, whihc is the partial result, built up as you calculate the answer. The consequence is that each time you return the recursive call, the answer is the full answer to the function, since you are holding the partial answer in a variable. Considering the VM stack again, the situation is vastly improved. Because the recursive method returns a call to itself each time, with no further operations needed i.e., the recursive caller actually exits with the call to recurse, there is no need to keep any calls on the stack except for the current one. factorial1b20,1 is put on the stack, but this exits with a call to factorial1b19,20 , which replaces the call to factorial1b18,380 , which is in turn replaced by the call to factorial1b17,6840 , and so on, until factorial1b2, ... returns just the result. Generally, the advice for dealing with methods that are naturally recursive because that is the natural way to code them for clarity is to go ahead with the recursive solution. You only need to spend time counting the cost if any when your profiling shows that this particular method call is a bottleneck in the application. At that stage, it is worth pursuing alternative implementations or avoiding the method call completely with a different structure. In case you need to tune a recursive algorithm or convert it into an iterative one, I provide some examples here. I start with an extremely simple recursive algorithm for calculating factorial numbers, as this illustrates several tuning points: public static long factorial1int n { if n 2 return 1L; else return nfactorial1n-1; } I have limited the function to long values, which means that you cannot use the function beyond factorial 20, as that overflows the long data type. This is to keep the function simple for this illustration. - 163 - Since this function is easily converted to a tail-recursive version, it is natural to test the tail- recursive version to see if it performs any better. For this particular function, the tail-recursive version does not perform any better, which is not typical. Here, the factorial function consists of a