Jump to content

  • Log In with Google      Sign In   
  • Create Account


Can any recursive function be a tail-recursive function?


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
7 replies to this topic

#1 Alpha_ProgDes   Crossbones+   -  Reputation: 4688

Like
0Likes
Like

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?
Beginner in Game Development? 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 Posted Image
 
Spoiler

Sponsor:

#2 SiCrane   Moderators   -  Reputation: 9573

Like
1Likes
Like

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.

#3 Felix Ungman   Members   -  Reputation: 1014

Like
1Likes
Like

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:
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


#4 iMalc   Crossbones+   -  Reputation: 2301

Like
1Likes
Like

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.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms

#5 6677   Members   -  Reputation: 1058

Like
-2Likes
Like

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.

#6 mhagain   Crossbones+   -  Reputation: 7863

Like
0Likes
Like

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.


#7 Álvaro   Crossbones+   -  Reputation: 12991

Like
1Likes
Like

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:
#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.


#8 SiCrane   Moderators   -  Reputation: 9573

Like
3Likes
Like

Posted 03 July 2012 - 06:44 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.

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.

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

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.

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))





Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS