-
递归的定义:
递归是方法自身调用自身的一种技术,它通常用于解决需要反复执行相同操作的问题。
在递归中,一个问题被分解为更小的子问题,然后使用递归函数来逐个解决这些子问题,直到达到基本情况。
-
递归的基本要素:
两个基本要素:递归函数和终止条件。
递归函数定义了如何通过调用自身来解决子问题;
终止条件则是在问题规模缩小到一定程度时停止递归调用。
-
递归的适用场景:
递归通常用于解决树形结构或分治策略的问题。
例如,二叉树的遍历、排序、搜索等问题,以及分治算法中的合并排序、快速排序等。
-
递归的优缺点:
优点:代码简洁、易于理解、符合人的思维习惯等;
缺点:存在栈溢出、空间开销大等。
因此,在使用递归时需要注意控制递归深度,避免出现栈溢出的情况。
-
递归的调试技巧:
由于递归函数的调用是自上而下的,因此调试递归函数时可以采用自下而上的方法,从终止条件开始逐步调试,直到解决完整的子问题。
-
案例:
-
斐波那契数列
//它是一个由0和1开始,后面的每一项都由前两项相加得到的数列。斐波那契数列的前几项为:0、1、1、2、3、5、8、13、21、34、……
#include <iostream>
using namespace std;
//定义 fibonacci(n) 函数:
//这个函数使用递归的方式实现。并且递归意味着一个函数调用自己。
int fibonacci(int n) {
if (n <= 1) {
return n; //当n小于或等于1时,返回n本身。
} else {
return fibonacci(n-1) + fibonacci(n-2);
//否则,递归调用fibonacci(n-1)和fibonacci(n-2),并将它们的结果相加返回。
}
}
//在主函数中,我们使用一个循环输出斐波那契数列的前n项。
int main() {
int n = 10; // 输出斐波那契数列的前n项
cout << "Fibonacci sequence of " << n << " numbers: ";
for (int i = 0; i < n; i++) {
//这个循环从0开始,一直到n-1。这是因为在斐波那契数列中,第0项是0,第1项是1,以此类推,直到第n-1项。所以循环从0开始是正确的。如果我们想包括第n项,那么循环应该一直运行到n。
cout << fibonacci(i) << " ";
} //将返回的数字打印到屏幕上,后面跟着一个空格。
cout << endl; //endl作用:插入一个换行符
return 0;
}
-
阶乘
#include <iostream>
using namespace std;
//定义一个名为factorial的函数,该函数接受一个整数参数n并返回一个整数结果。
int factorial(int n) {
if (n == 0) {
return 1; //如果参数n为0,则返回1,因为0的阶乘定义为1。
} else {
return n * factorial(n-1);
//递归调用factorial函数,传入参数n-1,并将结果乘以n,直到递归到参数为0的情况,然后返回1。
//我的理解:从最大的开始递归:5*factorial(4),然后5*4*factorial(3)...递归至n=0时。
//前面的不变,后面的疯狂展开
}
}
int main() {
int num = 5;
cout << "The factorial of " << num << " is " << factorial(num) << endl;
return 0;
}
-
二分查找
//二分查找是一种高效的搜索算法,其时间复杂度为O(log n)。它通过将搜索范围不断缩小来找到目标元素。
#include <iostream>
using namespace std;
//定义一个名为binarySearch的函数,该函数接受四个参数:一个整数数组arr、左边界left、右边界right和一个目标元素target。
int binarySearch(int arr[], int left, int right, int target) {
if (right >= left) { //确保搜索范围有效。
int mid = left + (right - left) / 2; //计算中间元素的索引。
/*1.中间元素的索引是 left + (right - left) / 2 而不是 (left + right) / 2。原因是 (left + right) / 2 在某些情况下会产生溢出。当 left 和 right 的值非常大时,它们的和可能超过了整型数值的上限,导致整数溢出。
2.使用 (right - left) / 2;而不是 right - left 可以保证当 right 和 left 相等时,不会出现除0错误。因为当 right 和 left 相等时,表示数组中只有一个元素,此时中间元素的索引应该是 left。*/
if (arr[mid] == target) { //中间元素=目标元素
return mid; //找到了
} else if (arr[mid] > target) { //中间元素>目标元素
return binarySearch(arr, left, mid - 1, target); //将右边界的元素删除,在左边进行查找目标元素
//排除了 arr[mid],但保留了 arr[mid - 1]。
/*传入 mid - 1 作为右边界,而不是 mid。这是因为我们希望在下一次递归调用中,将搜索区间缩小为原来的一半,即 [left, mid - 1]。
如果传入 mid,则搜索区间会变为 [left, mid],这将导致我们查找的元素被排除在外,因为我们在计算 mid 时已经将 arr[mid] 排除在外了。*/
} else {
return binarySearch(arr, mid + 1, right, target); //否之,中间元素小于目标元素,在右半部分继续搜索。
//排除了 arr[mid],但保留了 arr[mid + 1]。同理
}
}
return -1; //如果没有找到目标元素,返回-1。
}
int main() {
int arr[] = {2, 3, 4, 10, 40};
int n = sizeof(arr) / sizeof(arr[0]);
//sizeof(arr) 返回整个数组的字节大小;
//sizeof(arr[0]) 返回数组中单个元素的字节大小。将两者相除,就可以得到数组中元素的数量。
int target = 10; //定义目标元素
int result = binarySearch(arr, 0, n - 1, target); //调用binarySearch函数并传入数组、搜索范围的左右边界和目标元素作为参数。
//左边界 0 右边界 n-1。因为:索引都是从0开始
if (result == -1) {
cout << "未找到目标元素";
} else {
cout << "找到目标元素的索引为: " << result << endl;
}
return 0;
}