为什么引入递归
一个比较浅显点的可能认为递归是无限循环而已,自己用循环完全可以代替。递归之所以现在还存在是因为递归可以产生无限循环体,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来解递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归
- 递归函数
递归就是一个函数在它的函数体内调用它自身。执行递归函数将反复调用其自身,每调用一次就进入新的一层。 - 递归三个要素
函数功能:明确递归函数的功能有助于理解为什么使用递归执行以及递归过程
结束条件:这个是递归函数必须有的,如果没有结束条件,那么不仅导致无限循环,且造成计算机空间的极大浪费。递归调用,只有走到最后的结束点后函数才能依次退出,而未到达最后的结束点之前,占用的栈空间一直没有释放,如果递归调用次数过多,就可能导致占用的栈资源超过线程的最大值,从而导致栈溢出,导致程序的异常退出。
接近结束条件:即每次递归调用后都接近于定义的结束条件。
递归与栈的关系
为什么要将栈和递归联系在一起,接下来我们讨论栈与递归的关系,这更有助于理解递归过程。
- 例子
程序的目的是把一个整数从二进制形式转换为可打 印的字符形式。例如,给出一个值4267,我们需要依次产生字符’4’、‘2’、‘6’和’7’。
首先我们可以这样实现这个过程
4267%10=7
7+'0'='7' //将整数7转化为可以打印的字符7
4267/10=426
426%10=6
6+'0'=6
426/10=42
。。。。。。。循环执行,直到产生所有字符结束
这个问题本来直接用循环就可以实现,但是我们执行时产生的次序正好相反,这样我们可能还需要一个循环在逆序输出。
- 下面用递归实现来解决
void binary_to_ascii( unsigned int value ){
unsigned int quotient;
quotient = value / 10;
if( quotient != 0 ) // 递归终止条件
binary_to_ascii( quotient );
putchar( value % 10 + '0' );
该过程为
1.将参数值除以10。
2. 如果quotient的值为非零,调用binary to ascii打印quotient当前值的各位数字。
3. 接着,打印步骤1中除法运算的余数。
- 深入理解递归过程(与堆栈的关系)
下面我们继续按这个例子理解递归的过程
递归函数执行过程的关键是理解函数中所声明的变量是如何存储的。当函数被调用时,它的变量的空间是创建于运行时堆栈上的。以前调用的函数的变量仍保留在堆栈上,但它们被新函数的变量所掩盖,因此是不能被访问的。
当递归函数调用自身时,情况也是如此。每进行一次新的调用,都将创建一批变量,它们将掩盖递归函数前一次调用所创建的变量。当我们追踪一个递归函数的执行过程时,必须把分属不同次调用的变量区分开来,以避免混淆。
还是前面的代码
void binary_to_ascii( unsigned int value ){
unsigned int quotient;
quotient = value / 10;
if( quotient != 0 ) // 递归终止条件
binary_to_ascii( quotient );
putchar( value % 10 + '0' );
我们以图解形式展现这个过程
函数开始时堆栈的内容
接下来继续执行,quotient为426不为0,继续调用该函数。该函数第二次调用之初堆栈的内容为
堆栈上创建了一批新的变量,隐藏了前面的那批变量,除非当前这次递归调用返回,否则它们 是不能被访问的。再次执行除法运算之后,堆栈的内容如下:
接下来不断循环执
最后,quotient为0,函数调用结束,如图
到此,循环调用的过程结束。由于递归调用使这些语句重复执行,所以它的效果类似循环:当quotient的值非零时,把它的值作为初始值重新开始循环。但是,递归调用将会保存一些信息(这点与循环不同),也就是保存在堆栈中的变量值。这些信息很快就会变得非常重要。
接下来,因为quotient为0,所以函数不再调用,而是开始打印输出。然后函数返回,并开始销毁堆栈上的变量值。
首先输出4,4%10仍为4,+‘0’转化为字符
接下来输出42,%10为2,+‘0’转化为字符
不断执行类似例程
然后,这个递归函数就彻底返回到其他函数调用它的地点。如果你把打印出来的字符一个接一个排在一起,出现在打印机或屏幕上,你将看到正确的值:4267 。
到此,整个递归过程到此结束。
- 总结
总结一下整个递归过程,可以分为两个过程
递:也就是函数自己调用自己,不断的将变量值不断的以栈的的形式压入分配的空间中,期间要入栈中的值不能被访问。
归:符合结束条件,函数调用结束,然后变量值出栈,并释放变量原来占用的空间。
应用举例
为了进一步理解递归,我们再举几个简单的例子
1.计算阶乘
int f(int n){
if(n <= 1){ //结束条件
return n; //返回值,函数执行完毕,不再向下执行
}
return f(n-1) * n;
}
整个过程可以由下图来说明
图片来源:https://www.zhihu.com/question/31412436/answer/683820765
文章引用:C和指针