一.简介
1.介绍
递归(Recursion)在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法,其核心思想是分治策略。
在日常开发中,我们使用循环语句远远大于递归,但这不能说明递归就没有用武之地,实际上递归算法的解决问题的步骤更符合人类解决问题的思路,这是递归算法的优点,同时也是它的缺点。递归算法是比较好用,但是理解起来可能不太好理解,所以在递归算法和循环算法对比中,流行一句话:人理解循环,神理解递归。
2.什么是递归?
递归就是程序调用自身的编程技巧。递归通常把一个大型复杂的问题层层转化为一个与原问题相似,规模较小的问题来求解。递归策略只需要少量的程序就可以描述出解题过程所需要的多次重复的计算,大大减少程序的代码量。
递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
3.什么时候使用递归?
(1)大问题可以拆分成若干小问题。
(2)原问题与子问题除数据规模不同,求解思路完全相同。
(3)存在递归终止条件。
(4)当不满足终止条件时,要如何缩小函数值,并让其进入下一层循环中。
4.递归的两个基本要素:
边界条件:确定递归到何时终止,也称为递归出口。
递归模式:大问题是如何分解为小问题的,也称为递归体。递归函数只有具备了这两个要素,才能在有限次计算后得出结果
5.设计递归算法的步骤:
(1).确定递归公式
(2).确定边界(终了)条件
二.递归的简单示例:求阶乘
int fun(int num)
{
if(num == 0){
return 1;
}else{
return num *fun(num-1);
}
}
void main(void)
{
int result = fun(5);
printf("结果为:"%d\r\n", result);
}
结运行果为:
结果为:120
三.递归的基本原理
1.每一级的函数调用都有自己的变量。
2.每一次函数调用都会有一次返回。当程序执行到某一级递归的结尾处时,它会转移到前一级递归继续执行。程序不能直接返回到main()中的初始调用部分,而是通过递归的每一级逐步返回,即从func()的某一级递归返回到调用它的那一级。
3.递归函数中,位于递归调用前的语句和各级被调函数具有相同的执行顺序。
4.递归函数中,位于递归调用后的语句的执行顺序和各个被调函数的顺序相反。
5.虽然每一级递归都有自己的变量。但是函数代码并不会得到复制。函数代码是一系列的计算机指令。而函数调用就是从头执行相应函数的指令集,除了会每次创建变量,递归调用非常类似于一个循环语句。
6.递归函数中必须包含可以终止递归调用的语句。
四.递归的优缺点
1.优点
简洁清晰:递归能够将复杂的问题简化成更小的子问题,使得代码更加清晰易懂。
问题建模:递归能够自然地将问题建模成递归结构,使得问题的解决变得更加直观。
提高代码复用性:通过递归,可以在不同的情景中复用相同的解决方案。
2.缺点
性能损耗:递归调用涉及函数的重复调用和堆栈的频繁使用,可能会导致性能下降。
内存消耗:每次递归调用都需要在堆栈中存储函数的调用信息,可能会导致堆栈溢出的问题。
难以理解和调试:复杂的递归调用可能会导致代码的难以理解和调试,特别是递归函数中存在多个递归调用时。
五.应用场景:
实际上递归算法的使用场景,在排序算法,链表,树,图及其他只要符合分治思想的问题中,其实都可以采用递归来处理。
1.递归算法一般用于解决三类问题:
(1)数据的定义是按递归定义的。(例如:Fibonacci函数)
(2)问题解法按递归算法实现。
这类问题虽则本身没有明显的递归结构,但用递归求解比迭代求解更简单,如Hanoi问题。
(3)数据的结构形式是按递归定义的。
如二叉树、广义表等,由于结构本身固有的递归特性,则它们的操作可递归地描述。
2.常用场景
(1)树和图的遍历:树和图的结构天然适合递归的处理方式,如深度优先搜索(DFS)。
(2)分治算法:许多分治算法,如归并排序和快速排序,都是通过递归实现的。
(3)动态规划:动态规划问题中,递归可以帮助描述问题的递归结构,但通常需要使用记忆化搜索或者自底向上的迭代方式来提高性能。
(4)排列组合问题:许多排列组合问题,如子集、组合、排列等,可以通过递归实现。
3.代码示例
(1)求阶乘和
求 1!+2!+3!+4!+5!+6!+7!+…+n!的和。
代码演示:
int factorial(int n)
{
if (n == 1)
return 1;
return n * factorial(n - 1);
}
int main()
{
int n = 0;
int sum = 0;
int i = 0;
scanf("%d", &n);
for (i = 1; i <= n; i++)
{
sum += factorial(i);
}
printf("%d\n", sum);
return 0;
}
(2)求斐波那契额数列
这个数列从第3项开始,每一项都等于前两项之和。
代码:
int Fib(int n)
{
if (n <= 2)
return 1;
else
return Fib(n - 1) + Fib(n - 2);
}
int main()
{
int n = 0;
scanf("%d", &n);//20
int ret = Fib(n);
printf("%d\n", ret);
return 0;
}
(3)函数实现n的k次方
代码演示:
double Pow(n, k)
{
if (k > 0)
{
return n * Pow(n, k-1);
}
else if(k == 0)
{
return 1;
}
else
{
return 1.0 / Pow(n, -k);//实现指数为负数
}
}
int main()
{
int n = 0;
int k = 0;
scanf("%d %d", &n, &k);
double ret = Pow(n, k);
printf("%lf\n", ret);//double打印用lf
return 0;
}