Started by Jul 01 2012 09:03 PM

,
7 replies to this topic

Posted 01 July 2012 - 09:03 PM

It's a random question that just popped into my head. It's a bit of a beginner question as well, admittedly. However, I read up on recursion on wikipedia and wondered if any function that is recursive can be tail-recursive. Also, since it's an "optimization" of sorts, should one always strive to write a tail-recursive function anytime they need to write a recursive function?

External Articulation of Concepts Materializes Innate Knowledge of One's Craft and Science

**Beginner in Game Development? **Read here. And read here.

Super Mario Bros clone tutorial written in XNA 4.0 [MonoGame, ANX, and MonoXNA] by Scott Haley

If you have found any of the posts helpful, please show your appreciation by clicking the up arrow on those posts

Super Mario Bros clone tutorial written in XNA 4.0 [MonoGame, ANX, and MonoXNA] by Scott Haley

If you have found any of the posts helpful, please show your appreciation by clicking the up arrow on those posts

Spoiler

Posted 01 July 2012 - 10:11 PM

Yes, a recursive function can always be re-written as a tail recursive function. However, that may involve reimplementing the stack as one of the parameters to the tail recursive function, so there isn't always a benefit to making it tail recursive.

Posted 02 July 2012 - 12:17 AM

There are recursive functions that are not tail recursive. That is, not every recursive call is a tail call. A classical example is:

Note that the last operation is the addition of the results of the recursive calls, there are no tail calls to optimize.

int fib(int n) { if (n < 2) return n; else return fib(n-1) + fib(n-2); }

Note that the last operation is the addition of the results of the recursive calls, there are no tail calls to optimize.

openwar - the real-time tactical war-game platform

Posted 02 July 2012 - 12:31 AM

Anything can be rewritten an infinite number of ways, but in the purest sense, no not every recursive function can be rewritten tail-recursive instead. If it could then via a straightforward transformation we could change those functions to use iteration (which is generally more efficient) and have no use for recursion any more.

Using an explicit stack and tacking on some unrelated meaningless tail-recursion does not count as converting the algorithm to a tail-recursive implementation.

Using an explicit stack and tacking on some unrelated meaningless tail-recursion does not count as converting the algorithm to a tail-recursive implementation.

"In order to understand recursion, you must first understand recursion."

My website dedicated to sorting algorithms

My website dedicated to sorting algorithms

Posted 03 July 2012 - 02:55 AM

Fibonacci cannot be written tail recursively as said, Some sorting algorithms that are written recursively I believe can't be tail recursive either.

If your writing a function and it absolutely has to be done recursively for whatever reason (there are a few instances where recursion is handy) and it can be made as a tail-recursive function then do it.

When we did recursion in computer science it took one of the school computers 45 minutes to solve the Fibonacci sequence for n = 40 written in VB.net on .net version 4. Impossible to time the iterative approach with what we had available.

If your writing a function and it absolutely has to be done recursively for whatever reason (there are a few instances where recursion is handy) and it can be made as a tail-recursive function then do it.

When we did recursion in computer science it took one of the school computers 45 minutes to solve the Fibonacci sequence for n = 40 written in VB.net on .net version 4. Impossible to time the iterative approach with what we had available.

Posted 03 July 2012 - 03:27 AM

Classic tree-traversal can't be tail recursive either. Even in the simplest case - a BSP tree - only one of the recursions can be a tail recursion; the other must be a regular recursion.

It appears that the gentleman thought C++ was extremely difficult and he was overjoyed that the machine was absorbing it; he understood that good C++ is difficult but the best C++ is well-nigh unintelligible.

Posted 03 July 2012 - 03:32 AM

I think it is unfortunate that the example of Fibonacci is what most people remember when thinking of recursive functions, since it's a toy example and it gives people the incorrect impression that recursion is slow for the wrong reasons. That example is just a terrible way to compute Fibonacci numbers, but that doesn't mean that using recursion is the problem.

Here is a very fast implementation of Fibonacci numbers using recursion:

Depth-first search is the standard example that should come to mind instead. A recursive implementation is in this case the most natural, it's perfectly fast and small variations of it can be used for many purposes (e.g., to enumerate possibilities in many combinatorial problems, or to play chess).

Here is a very fast implementation of Fibonacci numbers using recursion:

#include <iostream> // M represents a matrix of the form // (a b ) // (b a+b) struct M { long a, b; M(long a, long b) : a(a), b(b) { } }; M operator*(M x, M y) { return M(x.a*y.a + x.b*y.b, x.a*y.b + x.b*y.a + x.b*y.b); } // Fast exponentiation M pow(M x, unsigned n) { if (n==0) return M(1,0); if (n==1) return x; if (n%2==0) return pow(x*x,n/2); if (n%3==0) return pow(x*x*x,n/3); return x*pow(x,n-1); } // Compute // (0 1)^n = (fib(n-1) fib(n) ) // (1 1) ( fib(n) fib(n+1)) long fib(unsigned n) { M m = pow(M(0,1),n); return m.b; } int main() { std::cout << fib(40) << '\n'; }

Depth-first search is the standard example that should come to mind instead. A recursive implementation is in this case the most natural, it's perfectly fast and small variations of it can be used for many purposes (e.g., to enumerate possibilities in many combinatorial problems, or to play chess).

**Edited by alvaro, 03 July 2012 - 11:21 AM.**

Posted 03 July 2012 - 06:44 AM

By that logic, there's "no use for recursion" in tail recursive functions at all and hence no point to the idea of tail recursion, because an existing tail recursive function can be trivially transformed into an iterative form as well. Just update the parameter variables and jump to the beginning of the function body, and you have your iterative version. However, not all languages are the same in terms of control structures, and iteration is non-idiomatic in many, if not most, functional languages. In those languages a tail recursive transformation has the same process and the same point as a manual transformation to an iterative version in an imperative language. In other words, a completely pointless waste of programmer time and computing resources in the ordinary case, but useful once in a while. Ultimately, they all express same algorithm, the difference is in the control structures used to implement that algorithm: regular recursion, tail recursion or iteration. Whether or not the change of expression of an algorithm represents a different "function" is a semantic argument.Anything can be rewritten an infinite number of ways, but in the purest sense, no not every recursive function can be rewritten tail-recursive instead. If it could then via a straightforward transformation we could change those functions to use iteration (which is generally more efficient) and have no use for recursion any more.

Using an explicit stack and tacking on some unrelated meaningless tail-recursion does not count as converting the algorithm to a tail-recursive implementation.

Incorrect. Fibonacci can be written tail recursively. There's just no point to writing the naive implementation tail recursively because it requires explicitly duplicating the stack structure that the recursive calls automatically generate, and this implicit stack is almost always faster than any explicit stack structure. Any recursive algorithm can be written to use an explicit stack in place of an implicit stack of function calls. This includes Fibonacci, tree-traversal, searches, sorts or whatever recursive algorithm you can imagine. You push data on an explicitly managed stack that would normally be passed as parameters in a recursive call and then pop the stack as computations progress. Again, this is the same process that you would use to transform a recursive function into an iterative version of the same function. The only difference that instead of a loop operating on the explicit stack you use a tail recursive call on the explicit stack.Fibonacci cannot be written tail recursively as said, Some sorting algorithms that are written recursively I believe can't be tail recursive either.

And that's just for a naive transformation. Actual examination of the algorithm can result in more sophisticated transformations. As an example, Dijkstra's algorithm in a tail-recursive Scheme implementation, which is essentially the same as alvaro's algorithm minus the special case for exponentiation of powers of three:

(define (fib n) (define (fib-aux a b p q count) (cond ((= count 0) b) ((even? count) (fib-aux a b (+ (* p p) (* q q)) (+ (* q q) (* 2 p q)) (/ count 2))) (else (fib-aux (+ (* b q) (* a q) (* a p)) (+ (* b p) (* a q)) p q (- count 1))))) (fib-aux 1 0 0 1 n))