C语言-什么是尾递归

1 尾递归简介

想必大家都知道递归是什么,第一次接触尾递归,首先要从它的定义说起:

尾递归:当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作。

举一个简单的例子,用递归算阶乘:

int factorial(int n)
{ 
    if(n == 0 || n == 1) {
	    return 1;
	} else {
		return n * factorial(n-1); 
	}
}

现在我们模拟一下程序的过程,例如当 n = 5 的时候:

factorial(5)
5 * factorial(4)
5 * (4 * factorial(3))
5 * (4 * (3 * factorial(2)))
5 * (4 * (3 * (2 * factorial(1))))
5 * (4 * (3 * (2 * 1)))
5 * (4 * (3 * 2))
5 * (4 * 6)
5 * 24
120

上述的递归过程中,“最长”的部分,即代表着此刻占用的栈内存最大,所以在一般的递归中,它的内存占用,先是增大,后来到达一个峰值,然后缩小。

下面给出尾递归的实现方法,看到这段代码后,第一印象会发现函数参数多了一个 tmp,事实上 tmp 代表着 1!(1的阶乘的值)也就是 1 1 1 ,于是在使用函数的时候,第二个参数传递一个 1 1 1

int factorial(int n, int tmp)
{
    if(n == 0) {
	    return 1;
	} else if (n == 1) {
		return tmp;
	} else {
    	return factorial(n - 1, n * tmp);
	}
}

我们同样模拟一下程序的过程,例如当 n = 5 的时候:

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

通过这个过程,我们可以发现,它完全对应着尾递归的定义,即当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作

尾递归比线性递归(第一个例子称为线性递归)多一个参数,这个参数是上一次调用函数得到的结果,并且每次递归的时候都会保留着上次递归的结果,它不属于表达式的一部分,而且它需要回归的数据,本身已经通过参数携带,所以回归过程中不用做任何操作,所以,相比较线性递归,尾递归占用的栈内存是恒定的

2 总结

既然尾递归相比线性递归解决了线性递归致命的缺点(stack overflow风险),是不是更提倡使用尾递归呢?

答案不是的,参考数据结构与算法分析-C语言描述中的一个例子,打印一个链表:

/* Bad use of recursion : Printing a linked list */
/* No header */

void PrintList(List L)
{
	if (L != NULL) {
		PrintElement(L->Element);
		PrintList(L->Next);
	}
}

在这里使用尾递归就是一个不好的例子,因为没有什么需要存储,在递归结束调用的时候,实际上并没有需要存储的值,因此,我们就可以带着在第一次递归调用中已经用过的那些值, goto 到函数的顶部,下面是改进后的程序(记住,应该更加自然的使用 while 循环结果去去除尾递归,这里使用 goto 只是为了说明编译器是如何自动地去除尾递归)。

/* Printing a linked list non-recursively */
/* Uses a mechanical translation */
/* No header */

void PrintList(list L)
{
	top:
	if (L != NULL) {
		PrintElement(L->Element);
		L = L->Next;
		goto top;
	}
}

不用递归去打印一个链表,事实上尾递归的去除是如此的简单,以至于某些编译器可以自动完成,所以这些工作不用程序员去完成,但是即使如此,最好还是在编写程序时避免出现尾递归。

3 参考

  1. What is tail recursion - StackOverflow?
  2. 数据结构与算法分析:C语言描述(原书第2版) - Mark Allen Weiss
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值