简体   繁体   English

C ++中的Tail Recursion,带有多个递归函数调用

[英]Tail Recursion in C++ with multiple recursive function calls

I was reading this post on tail recursion. 我正在阅读关于尾递归的这篇文章

I'll copy the posted solution: 我将复制发布的解决方案:

unsigned int f( unsigned int a ) {
   if ( a == 0 ) {
      return a;
   }
   return f( a - 1 );   // tail recursion
}

I was wondering, what about if the result depends on several recursive function calls? 我想知道,如果结果取决于几个递归函数调用呢? For example: 例如:

unsigned int f( unsigned int a ) {
    if ( a == 0 ) {
          return a;
       }
    return f(a -1) + f( a - 1 );   
}

Would the code above be optimized by the compiler? 上面的代码会被编译器优化吗?

As it stands, tail recursion doesn't apply. 目前看,尾递归不适用。 But if you look at the end of the second answer on the question you linked to, you can see how to rewrite the function appropriately. 但是如果你看一下你所链接问题的第二个答案的结尾,你就可以看到如何恰当地重写这个功能。 Starting with 从...开始

unsigned int f( unsigned int a ) {
    if ( a == 0 ) {
          return a;
    }
    return f(a-1) + f(a-1);   
}

rewrite it as follows: 重写如下:

unsigned int f( unsigned int a ) {
    if ( a == 0 ) {
          return a;
    }
    return 2 * f(a-1);  
}

Even now, tail recursion still isn't directly applicable. 即使是现在,尾递归仍然不能直接应用。 We need to make sure the return is strictly of the form return f(....) . 我们需要确保返回严格地以return f(....)的形式。 Rewrite the function again: 再次重写该功能:

unsigned int f( unsigned int a, unsigned int multiplicative_accumulator = 1 ) {
    if ( a == 0 ) {
          return multiplicative_accumulator * a;
    }
    return f(a-1, multiplicative_accumulator * 2 );   
}

Now, tail recursion is applicable. 现在,尾递归适用。 This uses a default value for the multiplicative_accumulator (thanks @Pubby) in order that the first call to f can simply be f(x) , otherwise you would have to write something f(x,1) . 这使用multiplicative_accumulator的默认值(感谢@Pubby),以便第一次调用f可以简单地为f(x) ,否则你必须写一些f(x,1)

A couple of final notes thanks to @SteveJessop: 感谢@SteveJessop做了几个最后的笔记:

  • It was safe to change f(a+1)+f(a+1) to 2*f(a+1) because f has no side effects (printing, modifying the heap, that kind of thing). f(a+1)+f(a+1)更改为2*f(a+1)是安全的,因为f没有副作用(打印,修改堆,这种事情)。 If f did have side effects, that rewrite wouldn't be valid. 如果f确实有副作用,则重写无效。
  • The original was equivalent to (2*(2*(2*a)) (or more precisely, (((a+a)+(a+a))+((a+a)+(a+a))) ) whereas the current version is more like (((2*2)*2)*a) . This is fine, especially for ints, because multiplication is associative and distributive. But this wouldn't be exact equivalent for float , where you would probably get small rounding discrepancies. With floating point arithmetic, sometimes a*b*c can be slightly different from c*b*a . 原文相当于(2*(2*(2*a)) (或更确切地说, (((a+a)+(a+a))+((a+a)+(a+a))) )而当前的版本更像是(((2*2)*2)*a) 。这很好,特别是对于整数,因为乘法是关联的和分配的。但这对于float来说并不完全等价,其中你可能会得到小的舍入差异。使用浮点运算,有时a*b*c可能与c*b*a略有不同。

第二个函数不是尾递归的,不能用循环轻松替换,所以很可能编译器不会这样做。

This is not a tail recursion (where the result of the function is the result of a recursive call): there is an operation to do after the recursion (the addition). 这不是尾递归(函数的结果是递归调用的结果):在递归(添加)之后有一个操作要做。 There is a more complex transformation (which depends on the commutativity of addition) to get a tail recursion: add an auxilliary function with an accumulator: 有一个更复杂的转换(取决于加法的交换性)来获得尾递归:使用累加器添加辅助函数:

unsigned int f_helper(unsigned int a, unsigned int acc)
{
   if (a == 0) {
      return acc;
   }
   return f_helper(a-1, f(a-1)+acc);
}

unsigned int f(unsigned int a) {
    if (a == 0) {
          return a;
    }
    return f_helper(a-1, f(a-1));
}

which you can transform into a loop 你可以转换成一个循环

unsigned int f_helper(unsigned int a, unsigned int acc)
{
   while (a != 0) {
      acc += f(a-1);
      a = a-1;
   }
   return acc;
}

unsigned int f(unsigned int a) {
    if (a == 0) {
       return a;
    }
    return f_helper(a-1, f(a-1));
}

then put it back into f 然后把它放回f

unsigned int f( unsigned int a ) {
  if (a == 0) {
      return a;
   }
   unsigned acc = f(a-1);
   a = a-1;
   while (a != 0) {
      acc += f(a-1);
      a = a-1;
   }
   return acc;
}

I would expect that it won't be optimized, as mentioned by others, but often these types of issues can be resolved by using memoization, which trades off using memory instead of making another recursive call. 我希望它不会像其他人所说的那样进行优化,但通常可以通过使用memoization来解决这些类型的问题,memoization使用内存而不是进行另一次递归调用。

For a nice explanation using C++ you can look at http://marknelson.us/2007/08/01/memoization/ . 有关使用C ++的一个很好的解释,您可以查看http://marknelson.us/2007/08/01/memoization/

In your example the last call could be replaced with 在您的示例中,最后一个呼叫可以替换为

return 2 * f(a - 1);

and that would be optimizable. 这将是可以优化的。

In many cases where it appears that tail-recursion won't work, it may be that you just need to look at your algorithm, and make some changes to help get this optimization. 在很多情况下,尾部递归似乎不起作用,可能只需要查看算法,并进行一些更改以帮助实现此优化。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM