Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/selenium/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 尾部递归对传统递归有什么帮助?_C++_C_Recursion_Tail Recursion - Fatal编程技术网

C++ 尾部递归对传统递归有什么帮助?

C++ 尾部递归对传统递归有什么帮助?,c++,c,recursion,tail-recursion,C++,C,Recursion,Tail Recursion,我读了关于尾部递归和传统递归之间的区别,发现它提到“但是尾部递归是一种不使用任何堆栈空间的递归形式,因此是一种安全使用递归的方法。” 我很难理解怎么做 用传统和尾部递归法求一个数的阶乘的比较 传统递归 /* traditional recursion */ fun(5); int fun(int n) { if(n == 0) return 1; return n * fun(n-1); } /* tail recursion */ fun(5,1) int fun(int

我读了关于尾部递归和传统递归之间的区别,发现它提到“但是尾部递归是一种不使用任何堆栈空间的递归形式,因此是一种安全使用递归的方法。”

我很难理解怎么做

用传统和尾部递归法求一个数的阶乘的比较

传统递归

/* traditional recursion */
fun(5);


int fun(int n)
{
 if(n == 0)
  return 1;


 return n * fun(n-1);
}
/* tail recursion */
fun(5,1)


int fun(int n, int sofar)
{
 int ret = 0;


 if(n == 0)
  return sofar;


 ret = fun(n-1,sofar*n);


 return ret;
}
在这里,调用堆栈看起来像

5 * fact(4)
      |
   4 * fact(3)
          |
       3 * fact(2)
             |
         2 * fact(1)
               |
            1 * fact(0)
                  |
                  1
尾部递归

/* traditional recursion */
fun(5);


int fun(int n)
{
 if(n == 0)
  return 1;


 return n * fun(n-1);
}
/* tail recursion */
fun(5,1)


int fun(int n, int sofar)
{
 int ret = 0;


 if(n == 0)
  return sofar;


 ret = fun(n-1,sofar*n);


 return ret;
}
然而,即使在这里,变量“sofar”在不同的点上也会保持-5,20,60120120。 但是,一旦从基本情况(递归调用4)调用了return,它仍然必须返回120到递归调用3,然后返回到#2,#1,然后返回到main。 所以,我的意思是使用堆栈,每次您返回到上一个调用时,都可以看到该时间点的变量,这意味着它在每个步骤都被保存

除非尾部递归是这样写的,否则我无法理解它是如何节省堆栈空间的

/* tail recursion */
fun(5,1)

int fun(int n, int sofar)
{
 int ret = 0;


 if(n == 0)
  return 'sofar' back to main function, stop recursing back; just a one-shot return


 ret = fun(n-1,sofar*n);


 return ret;
}

PS:我读了一些关于SO的文章,并且理解了什么是尾部递归,然而,这个问题更多的是关于为什么它可以节省堆栈空间。我在讨论这个问题的地方找不到类似的问题。

诀窍是,如果编译器注意到尾部递归,它可以编译一个
goto
。它将生成类似以下代码的内容:

int fun_optimized(int n, int sofar)
{
start:
    if(n == 0)
       return sofar;

    sofar = sofar*n;
    n = n-1;
    goto start;
}
正如您所看到的,堆栈空间在每次迭代中都被重用


请注意,只有当递归调用是函数中的最后一个操作,即尾部递归时,才能进行此优化(尝试对非尾部情况手动执行此操作,您会发现这是不可能的)。

当函数调用(递归)作为最终操作执行时,函数调用是尾部递归的由于当前递归实例在该点执行完毕,因此无需维护其堆栈帧

在这种情况下,在当前堆栈帧的顶部创建堆栈帧只不过是浪费。
当编译器将递归识别为尾部递归时,它不会为每个调用创建嵌套堆栈帧,而是使用当前堆栈帧。这实际上相当于一个
goto
语句。这使得函数调用是迭代的,而不是递归的

请注意,在传统递归中,每个递归调用必须在编译器执行乘法操作之前完成:

fun(5)
5 * fun(4)
5 * (4 * fun(3))
5 * (4 * (3 * fun(2)))
5 * (4 * (3 * (2 * fun(1))))
5 * (4 * (3 * (2 * 1)))
120  
这种情况下需要嵌套堆栈框架。有关更多信息,请参阅

在尾部递归的情况下,每次调用
fun
,变量
sofar
都会更新:

fun(5, 1)
fun(4, 5)
fun(3, 20)
fun(2, 60)
fun(1, 120)
120  

无需保存当前递归调用的堆栈帧。

您对什么是尾部递归感到困惑。看这个:我想知道这个短语。我只知道tail-chaining,如果像
return f()
那样使用,它只会跳到
f
,而不是创建一个新的堆栈框架,调用
f()
并返回结果。如果尾部链接,
f()
然后直接返回到原始调用方。然而,这是一个编译器优化的问题;你应该相信这一点。编译器可能对如何/如果到尾链有不同的想法。尾递归是传统的递归。或者更确切地说,它是递归的一种形式;但它可能是所有形式中最传统的。请参阅,例如:翻译成伪代码,它可能是:
def is_in_N(x):如果x==0,则返回true,否则返回is_in_N(x-1)
需要尾部调用才能对其进行尾部调用优化,但尾部调用本身并不保证优化。对于一个要求TCO的类似于语言的方案,这没有问题,您可以说尾部递归不使用额外的堆栈空间,但对于所有其他不要求尾部递归的语言,尾部递归本身并不意味着您不使用堆栈。C++是在这个范畴中的。只进行尾部调用的函数是一个迭代过程,而至少有一个调用不在尾部位置的递归是一个递归过程。这与传统无关。