一、递归函数的特点和使用场景是什么?
递归函数的特点:
递归函数是自己调用自己的函数。特点是代码简洁,但可能会导致栈溢出。编写递归函数时,必须告诉它何时停止递归,否则将会导致无限循环从而导致栈溢出错误,因此每个递归函数都有两个部分:基线条件(终止条件)和递归条件。
基线条件(终止条件):指的是函数不再调用自己,从而避免形成无限循环;
递归条件:指的是函数调用自己。
递归函数的使用场景
1. 搜索算法:
深度优先搜索(DFS):在树或图的数据结构中,DFS通过递归地访问节点的子节点来遍历整个结构。
广度优先搜索(BFS):虽然BFS通常不直接使用递归实现,但在某些情况下,可以通过递归地访问层的节点来模拟BFS。
3.图论算法:
连通性检测:判断图中的两个节点是否可以通过一系列边相连,这可以通过递归的深度优先搜索或广度优先搜索实现。
2. 动态规划:
动态规划算法的核心思想之一是将大问题分解为小问题,并存储子问题的解以避免重复计算。递归是这种思想的一个自然实现方式。
3. 排序算法:
快速排序、归并排序等算法都使用了递归的思想。它们通过将数组划分为更小的子数组,然后递归地对子数组进行排序,最后将子数组的排序结果合并起来得到最终排序结果。
4. 树和图的处理:
树的遍历(前序、中序、后序)、图的遍历(DFS、BFS)等操作通常使用递归实现。
5. 数学计算:
阶乘、幂、斐波那契数列等数学计算经常通过递归函数来实现。递归可以将复杂的计算过程简化为更小的子问题,从而更容易理解和实现。
6. 文件操作:
在文件系统中,遍历文件夹及其子文件夹中的所有文件时,递归是一个常见的解决方案。
递归计算阶乘:
#include<iostream>
using namespace std;
// 递归函数计算阶乘
int fun(int n) {
if (n == 0 || n == 1) {
return 1;
}
else {
return n * fun(n - 1);
}
}
int main() {
int num;
cin >> n;
int ans = fun(num);
printf("%d 的阶乘为: %d\n", num, ans);
return 0;
}
输入
5
输出
5 的阶乘为: 120
递归计算斐波那契数列:
#include<iostream>
using namespace std;
// 递归函数计算斐波那契数列
int fun(int n) {
if (n == 1 || n == 2) {
return 1;
}
else {
return fun(n - 1) + fun(n - 2);
}
}
int main() {
int num ;
cin >> num;
int ans = fun(num);
printf("第 %d 个斐波那契数为: %d\n", num, ans);
return 0;
}
输入
6
输出
第 6 个斐波那契数为: 8
二、什么是回调函数?有什么特点?
回调函数是一种将函数作为参数传递给另一个函数的技术,接收方可以在某个时刻执行这个回调函数。回调函数本质上也是普通函数,只是调用机制有所区别——首先通过传参的形式将该函数的地址传递给其他函数,然后在其他函数中通过函数指针调用该函数。在其他函数中通过函数指针调用该函数的过程称为回调,而作为被调用的该函数则被称为回调函数。
调用者不必关心如何实现特定的功能,只需要知道在需要时调用回调函数即可。代码可以动态地改变行为,运行时可以通过不同的回调函数来定制不同的行为。在异步编程中,回调函数能够在任务完成时执行特定的操作,而不会阻塞主线程。特别适用于处理异步事件,如图形界面的按钮点击、网络数据处理等场景。
使用回调函数可能增加代码的复杂性,特别是在多层回调或嵌套回调的情况下,代码可能难以维护和理解。由于回调函数的执行时机取决于外部条件(如事件或异步操作),调试和跟踪回调函数可能比普通同步代码更难。
回调函数的实现:
#include<iostream>
using namespace std;
// 定义一个普通函数
void fun(int res) {
cout << "Res: " << res << endl;
}
// 定义接收回调函数的函数
void process(int a, int b, void(*callback)(int)) {
int res = a + b;
callback(res); // 调用回调函数
}
int main() {
process(2, 3, fun); // 传递回调函数
return 0;
}
输出
Res: 5