The Beauty of Recursion
Contents
Declaration: this article is in long time editing…
Here comes some beautiful recursive solutions to some problems.
Examples
Some of the problems have a very nice recursive structure, we can deal with them just using one step recursion.
Fibonacci Numbers
The first comes very famous Fibonacci Numbers, which is a sequence of 0, 1, 1, 2, 3, 5, 8, 13 … The structure is easily captured, if we use $\text{fib}(n)$ to denote the $n^{\text{th}}$ Fibonacci Number (n is assumed to start from 0). $$ \text{fib}(n) = \begin{cases} n, &\text{ if } n \le 1 \newline \text{fib}(n-1) + \text{fib}(n-2), &\text{ if } n > 1. \end{cases} $$ That is why we can write easily a procedure to compute the fibs. If we use MIT Scheme, we can write as follows:
|
|
or write a iterative version, since the above recursion has time complexity $O(2^n)$,
|
|
This is called tail recursion in Lisp, it generates a iterative process so that in scheme it is iterative, though in many other languages it may belong to recursive thing. Following is a cpp version.
|
|
Jump Floor
A frog is jumping, it can either jump one or two steps at a time. How many ways can he jump from 0 to nth floor? For instance, from 0 to 3, there are 3 ways:
- 1,2 (first one, then it jumps two steps and reach 3)
- 2,1 (first two, then it jumps one step and reach 3)
- 1,1,1
Make sure you catch the problem. It will indeed reduce to Fibonacci Numbers. Let’s see. Always try the First-Case Analysis. Denote $\text{opt}(n)$ to be the number of ways it jumps from 0 to $n^{\text{th}}$ floor. If we condition on the first step, then
- it jumps 1 at first time, then the remaining is the number of ways to jump from 0 to $(n-1)^{\text{th}}$ floor, which is $\text{opt}(n-1)$.
- it jumps 2 at first time, then the remaining $\text{opt}(n-2)$.
Hence we get the following: $$ \text{opt}(n) = \begin{cases} n, & n \le 2 \newline \text{opt}(n-1) + \text{opt}(n-2), & n > 2. \end{cases} $$ Note that the above is a Fibonacci sequence except the index shifting.
|
|
Linked-List Reverse
You are given a linked list, which is defined by a cxx struct
or class
. Can you output the reversed linked list?
|
|
Reverse Print
You are given a linked list, can you print it’s elements from end to begining?
|
|
Conclusions
What can we do use recursions? How shall we consider when wrting recursions? The most important thing, I think, is closure. You must have the experience that some day you were working with a recursive procedure, getting stuck in the conditions and inputs/outputs of the recursion, you came up with a mess when trying to understanding or simulating the process in your brain, and finally, you even did not know why it works, what happened inside it, how did it reach the boundary cases, etc.
The key is not to consider! Stop digging your pit and bury yourself. Leave all the details behind, all your focus is just one recursion step. You deal with each recursion step like a blackbox, with its inputs and outputs. Each time, you should form a closure. Specifically, you must formulate your outputs of the current recursion step so that it can fit to next recursion step as a input. And you are focusing to find the common pattern that each recursion step should do. That depends on situations, but you should have the ability to extract some common pattterns in some problems.
Do not forget your base cases (or boundary cases, stopping rules). That’s the export of recursion. Once you have done in recursion steps, the next thing to consider is the base cases. Where can the procedure exit, how many cases will it reach. Usually these base cases are very hard to find, and can be very confusing. Be careful to deal with them.
Once you had completed the above, you are almost done! Now you are free of those confusing details. The key is sometimes you know what you have no need to consider.