【C语言】递归
目录
一、什么是递归
二、递归的限制条件
三、递归举例
1.求n的阶乘
思路
使用函数实现
画图推演
2.顺序打印一个整数的每一位
思路
使用函数实现
画图推演
递归的缺陷
3.求第n个斐波那契数
一、什么是递归
递归就是函数自己调用自己
把一个大型复杂问题层层转化为一个与原问题相似,但规模较小的子问题来求解;直到子问题不能再被拆分,递归就结束了。所以递归的思考方式就是把大事化小的过程。递归中的递就是递推的意思,归就是回归的意思。
二、递归的限制条件
- 递归存在限制条件,当满足这个限制条件的时候,递归便不再继续。
- 每次递归调用之后越来越接近这个限制条件。
三、递归举例
1.求n的阶乘
思路
我们知道求n!的公式:n! = n * (n - 1)!
当n==0时,n!==1
使用递归的思想求5!:
想要求5!,只要求出4!,5! = 5 * 4!
想要求4!,只要求出3!,4! = 4 * 3!
想要求3!,只要求出2!,3! = 3 * 2!
想要求2!,只要求出1!,1! = 1 * 0!
想要求1!,只要求出0!,0! = 1
当我们知道最小的组成部分之后往前回推,就能求得5!
如此,先从大往小递推,再从小往大回归就是递归的执行流程
使用函数实现
创建计算阶乘的函数,先确定n==0时的阶乘,当n>0时,函数就自己调用自己,抽丝剥茧直到0!,然后再回归
int Fact(int n)
{if(n==0)return 1;elsereturn n*Fact(n-1);
}
画图推演
2.顺序打印一个整数的每一位
思路
比如
输入:1234
输出:1 2 3 4
1234%10==4,1234/10==123,这就相当于去掉了4
123%10==3,123/10==12,这就相当于去掉了3,
以此类推,不断的 %10 和 /10 操作,直到1234的每一位都得到,但是这里有个问题就是得到的数字顺序是倒着的,即输出结果是4 3 2 1
但是我们发现其实一个数字的最低位是最容易得到的,通过%10就能得到,那我们假设想写一个函数Print来打印n的每一位,如下表示:
Print(n)
如果n是1234,那表示为
Print(1234) //打印1234的每一位
其中1234中的4可以通过%10得到,那么Print(1234)就可以拆分为两步:
- Print(1234/10) //打印123的每⼀位
- printf(1234%10) //打印4
完成上述2步,那就完成了1234每一位的打印
那么Print(123)又可以拆分为两步
- Print(123/10) //打印12的每⼀位
- printf(123%10) //打印3
以此类推
Print(1234)
==>Print(123) + printf(4)
==>Print(12) + printf(3)
==>Print(1) + printf(2)
==>printf(1)
直到被打印的数字变成一位数的时候,就不需要再拆分,递归结束。
使用函数实现
void Print(int n)
{ if(n > 9) //如果n不是一位数,就将n降一位后继续调用拆分打印函数{Print(n/10); }printf("%d ", n%10); //打印n的最低位
}
画图推演
递归的缺陷
在C语言中每一次函数调用,都要需要为本次函数调用在栈区申请一块内存空间来保存函数调用期间的各种局部变量的值,这块空间被称为运行时堆栈,或者函数栈帧。函数不返回,函数对应的栈帧空间就⼀直占用,所以如果函数调用中存在递归调用的话,每一次递归函数调用都会开辟属于自己的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间。所以如果采用函数递归的方式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢出(stack overflow)的问题。
所以如果不想使用递归就得想其他的办法,通常就是迭代的方式(通常就是循环的方式)。比如:计算n的阶乘,也是可以产生1~n的数字累计乘在一起的。
int Fact(int n)
{int i = 0;int ret = 1;for(i=1; i<=n; i++){ret *= i;}return ret;
}
事实上,我们看到的许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更加清晰,但是这些问题的迭代实现往往比递归实现效率更高。
当一个问题非常复杂,难以使用迭代的方式实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。
3.求第n个斐波那契数
我们也能举出更加极端的例子,就像计算第n个斐波那契数,是不适合使用递归求解的,但是斐波那契数的问题通过是使用递归的形式描述的,如下:
看到这公式,很容易诱导我们将代码写成递归的形式如下:
int Fib(int n)
{if(n<=2)return 1;elsereturn Fib(n-1)+Fib(n-2);
}
当我们n输入为50的时候,需要很长时间才能算出结果,这个计算所花费的时间,是我们很难接受的, 这也说明递归的写法是非常低效的,那是因为递归程序会不断的展开,在展开的过程中会有重复计算,而且递归层次越深,冗余计算就会越多。
在计算第40个斐波那契数的时候,使用递归方式,第3个斐波那契数就被重复计算了 39088169次,这些计算是非常冗余的。所以斐波那契数的计算,使用递归是非常不明智的,我们就得想如何使用迭代的方式来解决。
我们知道斐波那契数的前2个数都是1,然后前2个数相加就是第3个数,那么我们从前往后,从小到大计算就行了。 这样就有下面迭代实现的代码:
int Fib(int n)
{int a = 1;int b = 1;int c = 1;while(n>2){c = a+b;a = b;b = c;n--;}return c;
}
迭代的方式去实现这个代码,效率就要高出很多了。
所以递归虽好,滥用却不行,适可而止即可。