Prof. S.M. Lee Department of Computer Science
Prof. S.M. Lee Department of Computer Science
Answer: Answer:
Answer:
Answer:
Recursion
Recursion is more than just a programming technique. It has two other uses in computer science and software engineering, namely: as a way of describing, defining, or specifying things. as a way of designing solutions to problems (divide and conquer).
Recursion
Recursion can be seen as building objects from objects that have set definitions. Recursion can also be seen in the opposite direction as objects that are defined from smaller and smaller parts. Recursion is a different concept of circularity.(Dr. Britt, Computing Concepts Magazine, March 97, pg.78)
Iterative Definition
In general, we can define the factorial function in the following way:
Iterative Definition
This is an iterative definition of the factorial function. It is iterative because the definition only contains the algorithm parameters and not the algorithm itself. This will be easier to see after defining the recursive implementation.
Recursive Definition
We can also define the factorial function in the following way:
if n=0 if n>0
n x (n-1) x (n-2) x x 2 x 1
Recursive
factorial(n) = 1 n x factorial(n-1)
Function calls itself
if n=0 if n>0
Recursion
To see how the recursion works, lets break down the factorial function to solve factorial(3)
Breakdown
Here, we see that we start at the top level, factorial(3), and simplify the problem into 3 x factorial(2). Now, we have a slightly less complicated problem in factorial(2), and we simplify this problem into 2 x factorial(1).
Breakdown
We continue this process until we are able to reach a problem that has a known solution. In this case, that known solution is factorial(0) = 1. The functions then return in reverse order to complete the solution.
Breakdown
This known solution is called the base case. Every recursive algorithm must have a base case to simplify to. Otherwise, the algorithm would run forever (or until the computer ran out of memory).
Breakdown
The other parts of the algorithm, excluding the base case, are known as the general case. For example: 3 x factorial(2) general case 2 x factorial(1) general case etc
Breakdown
After looking at both iterative and recursive methods, it appears that the recursive method is much longer and more difficult. If thats the case, then why would we ever use recursion? It turns out that recursive techniques, although more complicated to solve by hand, are very simple and elegant to implement in a computer.
Iterative Algorithm
factorial(n) { i=1 factN = 1 loop (i <= n) factN = factN * i i=i+1 end loop return factN }
The iterative solution is very straightforward. We simply loop through all the integers between 1 and n and multiply them together.
Recursive Algorithm
Note how much simpler factorial(n) { the code for the recursive if (n = 0) version of the algorithm is return 1 as compared with the iterative version else return n*factorial(n-1) we have eliminated the end if loop and implemented the } algorithm with 1 if statement.
Basic Recursion
What we see is that if we have a base case, and if our recursive calls make progress toward reaching the base case, then eventually we terminate. We thus have our first two fundamental rules of recursion:
Basic Recursion
1. Base cases:
Always have at least one case that can be solved without using recursion.
2. Make progress:
Any recursive call must make progress toward a base case.
Limitations of Recursion
Recursion is a powerful problem-solving technique that often produces very clean solutions to even the most complex problems. Recursive solutions can be easier to understand and to describe than iterative solutions.
Limitations of Recursion
By using recursion, you can often write simple, short implementations of your solution. However, just because an algorithm can be implemented in a recursive manner doesnt mean that it should be implemented in a recursive manner.
Limitations of Recursion
Recursion works the best when the algorithm and/or data structure that is used naturally supports recursion. One such data structure is the tree (more to come). One such algorithm is the binary search algorithm that we discussed earlier in the course.
Limitations of Recursion
Recursive solutions may involve extensive overhead because they use calls. When a call is made, it takes time to build a stackframe and push it onto the system stack. Conversely, when a return is executed, the stackframe must be popped from the stack and the local variables reset to their previous values this also takes time.
Limitations of Recursion
In general, recursive algorithms run slower than their iterative counterparts. Also, every time we make a call, we must use some of the memory resources to make room for the stackframe.
Limitations of Recursion
Therefore, if the recursion is deep, say, factorial(1000), we may run out of memory. Because of this, it is usually best to develop iterative algorithms when we are working with large numbers.
Application
One application of recursion is reversing a list. Before we implemented this function using a stack. Now, we will implement the same function using recursive techniques.
Fibonacci function:
fibonacci(0) = 1 fibonacci(1) = 1 fibonacci(n) = fibonacci(n-1) + fibonacci(n-2) [for n>1]
This definition is a little different than the previous ones because It has two base cases, not just one; in fact, you can have as many as you like. In the recursive case, there are two recursive calls, not just one. There can be as many as you like.
copy of f
copy of f
copy of f
x = 0 y = ? return 1
copy of f
Conclusion
A recursive solution solves a problem by solving a smaller instance of the same problem. It solves this new problem by solving an even smaller instance of the same problem. Eventually, the new problem will be so small that its solution will be either obvious or known. This solution will lead to the solution of the original problem.
invariably recursive functions are clearer, simpler, shorter, and easier to understand than their non-recursive counterparts. the program directly reflects the abstract solution strategy (algorithm).
From a practical software engineering point of view these are important benefits, greatly enhancing the cost of maintaining the software.
The main thing to note here is that the variables that will hold the intermediate results, S1 and S2, have been declared as global variables
This sort of bug is very hard to find, and bugs like this are almost certain to arise whenever you use global variables to storeintermediate results of a recursive function.
Recursion is based upon calling the same function over and over, whereas iteration simply `jumps back' to the beginning of the loop. A function call is often more expensive than a jump.
Time: The operations involved in calling a function - allocating, and later releasing, local memory, copying values into the local memory for the parameters, branching to/returning from the function - all contribute to the time overhead.
If a function has very large local memory requirements, it would be very costly to program it recursively. But even if there is very little overhead in a single function call, recursive functions often call themselves many many times, which can magnify a small individual overhead into a very large cumulative overhead.
int factorial(int n) { if (n == 0) return 1; else return n * factorial(n-1); } There is very little overhead in calling this function, as it has only one word of local memory, for the parameter n. However, when we try to compute factorial(20), there will end up being 21 words of memory allocated one for each invocation of the function:
factorial(20) -- allocate 1 word of memory, call factorial(19) -- allocate 1 word of memory, call factorial(18) -- allocate 1 word of memory, . . . call factorial(2) -- allocate 1 word of memory, call factorial(1) -- allocate 1 word of memory, call factorial(0) -- allocate 1 word of memory,
at this point 21 words of memory
E.g.: void do_loop () { start: ...; if (e) goto start; } Notice that this optimization also removes the space overhead associated with function calls.
Most recursive algorithms can be translated, by a fairly mechanical procedure, into iterative algorithms. Sometimes this is very straightforward - for example, most compilers detect a special form of recursion, called tail recursion, and automatically translate into iteration without your knowing. Sometimes, the translation is more involved: for example, it might require introducing an explicit stack with which to `fake' the effect of recursive calls.
In general, there is no reason to incur the overhead of recursion when its use does not gain anything. Recursion is truly valuable when a problem has no simple iterative solution.