递归是程序运行时的一种现象,也是解决某些特定问题时较迭代算法来说更自然更优雅的代码组织方式。作为程序员工作了多年后,我发现除了用发量来区分一名程序猿是否优秀以外,能不能理解好递归、能不能用递归来解决问题也是区分好程序员和差程序员的试金石。很多同学通过学习掌握了某些语言的语法,也能写一些代码,但是一遇递归就头大。下面就来给大家总结总结。
谈到递归,我们的C语言老师可能会说递归就是「函数自己调用自己」。比如下面的代码段:
void foo()
{
// 自己调用自己就是递归
foo();
}
我们知道这个函数要是运行起来,除了让你的计算机报出一个「堆栈溢出」的错误外,其他什么作用也没有。在某种程度上说,我们的大脑就是一个计算机。当我们尝试去理解「自己调用自己」这句话时,大脑也会陷入一个无限的递归过程里,然后「轰」的一声「堆栈溢出」了,所以也就无法去理解了。当然老师还会告诉你递归除了「自己调用自己」外,还有很重要的一部分就是在满足条件的时候函数会返回,这样就避免出现无限递归的过程了。所以一个完整的递归函数组成如下:
void foo()
{
// 返回部分,递归的退出条件
if (condition)
{
return;
}
// 递归部分
foo();
}
接下来老师一般会举例如何用递归计算斐波那契数列数列,一般计算该数列的代码段如下:
unsigned int fibo(unsigned int n)
{
// 返回部分
if (n == 0)
return 0;
if (n == 1)
{
return 1;
}
// 递归部分
return fibo(n - 1) + fibo(n - 2);
}
代码只有短短几行,却可以很优雅的解决这样的问题。但是你真的能理解吗?要理解递归,我们要退一步,了解另一个概念——「分而治之」。稍微了解程序设计的人,对这个概念应该不陌生。通俗的说,假如我们有这样一个问题A,如果能把A分解成一系列比A更容易解决的子问题(A0,A1,A2……An),通过解决子问题(A0,A1,A2……An)来最终达成解决问题A,这个就是「分而治之」。从本质上来说递归就是「分而治之」概念的一个应用。以递归计算斐波纳切数列来举例,要计算斐波纳切数列的第n项该怎么办?通过数列的定义我们知道第n项的值等于第n-1项加上第n-2项,所以我们可以把计算第n项这个问题分解成计算n-1项和n-2项两个子问题。我们知道计算n-1和n-2项要比计算n项更容易点(因为n-1和n-2都比n要来得小)。那么n-1项由如何计算呢?根据定义n-1项等于(n-1)-1项和(n-1)-2项的值,好了,我们在这里碰到了递归,好,我们先就此打住。因为n在一直减少,最终会减到1,再减到0,而第0项和第1项的值我们不用计算就知道的,这就是递归终结的时候了。
唠叨了这么多,我们可以得出使用递归必须要满足的两个条件:
要有递归公式。
要有终止条件。
递归和循环的关系
递归和循环存在很多关系。理论上讲,所有的循环都可以转化成递归,但是利用递归可以解决的问题,使用循环不一定能解决。比如编写树和图的程序就必须用递归,虽然循环也可以实现,但那样做绝对是程序员的噩梦(为什么程序猿头发少?)。
循环又称迭代。递归算法与迭代算法设计思路的主要区别在于:函数或算法是否具备收敛性!当且仅当一个算法存在预期的收敛效果时,采用递归算法才是可行的。否则就不能使用递归算法。所谓收敛性就是指要有终止条件,不能无休止地递归下去。
递归的优缺点
递归的优点是简化程序设计,结构简洁清晰,容易编程,可读性强,容易理解。在很多情况下使用递归是必要的,它往往能把复杂问题分解为更简单的步骤,而且能够反映问题的本质。我们一开始可能发现递归理解起来也不容易,这是因为我们的「认知」太少了!
但是递归的缺点也很明显: 速度慢,运行效率低,对存储空间的占用比循环多。 严格讲,循环几乎不浪费任何存储空间,而递归浪费的空间实在是太大了,而且速度慢。
典型应用
应用一:汉诺塔问题
问题描述:相传在古印度圣庙中,有一种被称为汉诺塔(Hanoi)的游戏。该游戏是在一块铜板装置上,有三根杆(编号A、B、C),在A杆自下而上、由大到小按顺序放置64个金盘。游戏的目标:把A杆上的金盘全部移到C杆上,并仍保持原有顺序叠好。操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持大盘在下,小盘在上,操作过程中盘子可以置于A、B、C任一杆上。
#include <stdio.h>
int count;
void hanoi(int n,char a,char b,char c)
{
if(n==1)
{
printf("第%d次移动:%c->%c\n",++count,a,c);
}
else
{
hanoi(n-1,a,c,b);
printf("第%d次移动:%c->%c\n",++count,a,c);
hanoi(n-1,b,a,c);
}
}
int main(void)
{
int n;
char a='A',b='B',c='C';
printf("请输入汉诺塔层数\n");
scanf("%d",&n);
hanoi(n,a,b,c);
return 0;
}
应用二:递归方法十进制转化二进制
#include <stdio.h>
void shi_er(unsigned long n)
{
int r;
r=n%2;
if(n>=2)
{
shi_er(n/2);
}
putchar(r==0?'0':'1');
}
int main(void)
{
shi_er(10);
return 0;
}
应用三:阶乘
#include <stdio.h>
int factrial(int a)
{
int product=1;
if (a == 1)
{
return product;
}
else
{
product=a*factrial(a-1);
}
}
int main(int argc, char const *argv[])
{
printf("%d\n",factrial(3));
return 0;
}
更多精彩视频、文章、嵌入式学习资料,微信关注公众号 【学益得智能硬件】