算法详解-递归
创始人
2025-05-31 07:57:39
0

文章目录

  • 前言
  • 主要内容
    • 初级
      • 阶乘
      • 斐波那契数列
      • 汉诺塔
      • 数组求和
      • 幂运算
      • 数组翻转
      • 字符串翻转
    • 中级
      • 全排列
      • 子集
    • 高级
      • 正则表达式匹配
      • N皇后问题
  • 总结
    • 无限循环
    • 栈溢出
  • 更多宝藏


前言

😎🥳😎🤠😮🤖🙈💭🍳🍱
递归是一种非常重要的编程技巧,它可以让我们用简洁的代码解决复杂的问题。


主要内容

🦞🦐🦀🦑🦪

主要内容: 递归是一种算法,它通过调用自身来解决问题。一个递归函数通常包括两个部分:基本情况(base case)和递归情况(recursive case)。基本情况是指函数能够直接解决的最简单问题,而递归情况则是指函数通过调用自身来解决更小规模的问题。

初级

阶乘

举个例子,我们可以使用递归来计算阶乘。阶乘表示为n!,定义为n * (n-1) * (n-2) * … * 1。我们可以将其写成一个递归函数:

int factorial(int n) {if (n == 0) {return 1; // 基本情况} else {return n * factorial(n - 1); // 递归情况}
}

斐波那契数列

斐波那契数列:斐波那契数列的第n项定义为前两项之和(除了第0项和第1项分别为0和1)。我们可以使用递归来计算斐波那契数列的第n项:

#include 
using namespace std;int fib(int n) {if (n == 0 || n == 1) {return n;} else {return fib(n - 1) + fib(n - 2);}
}int main() {int n = 10;cout << fib(n) << endl;return 0;
}

在这个例子中,fib函数接受一个参数:整数n。函数使用递归来计算斐波那契数列的第n项。
当n等于0或者1时,直接返回n作为结果。否则,返回前两项之和。

汉诺塔

汉诺塔问题:汉诺塔是一个经典的递归问题。它包括三根柱子和一些大小不等的圆盘。初始时,所有圆盘都叠在第一根柱子上,按照从大到小的顺序排列。目标是将所有圆盘移动到第三根柱子上,并保持原有顺序。每次移动只能移动一个圆盘,并且不能将大圆盘放在小圆盘上面。

我们可以使用递归来解决汉诺塔问题。假设我们要将n个圆盘从柱子A移动到柱子C,可以分为以下步骤:

将前n-1个圆盘从柱子A移动到柱子B(借助柱子C)。
将最后一个圆盘从柱子A移动到柱子C。
将前n-1个圆盘从柱子B移动到柱子C(借助柱子A)。
其中步骤1和步骤3都是规模更小的汉诺塔问题,因此可以使用递归来解决。

#include 
using namespace std;void hanoi(int n, char A, char B, char C) {if (n == 1) {cout << "Move disk 1 from " << A << " to " << C << endl;} else {hanoi(n - 1, A, C, B);cout << "Move disk " << n << " from " << A << " to " << C << endl;hanoi(n - 1, B, A, C);}
}int main() {int n = 3; // 圆盘数量hanoi(n, 'A', 'B', 'C');return 0;
}

在这个例子中,hanoi函数接受4个参数:圆盘数量n,起始柱子A,辅助柱子B和目标柱子C。函数使用递归来解决汉诺塔问题,并输出每一步的移动过程。

当圆盘数量为1时,直接将圆盘从起始柱子移动到目标柱子。否则,先将前n-1个圆盘从起始柱子移动到辅助柱子(借助目标柱子),然后将最后一个圆盘从起始柱子移动到目标柱子,最后再将前n-1个圆盘从辅助柱子移动到目标柱子(借助起始柱子)。

数组求和

给定一个整数数组,使用递归计算数组中所有元素的和。

#include 
using namespace std;int sum(int arr[], int n) {if (n == 0) {return 0;} else {return arr[n - 1] + sum(arr, n - 1);}
}int main() {int arr[] = {1, 2, 3, 4};int n = sizeof(arr) / sizeof(arr[0]);cout << sum(arr, n) << endl;return 0;
}

在这个例子中,sum函数接受两个参数:整数数组arr和数组长度n。函数使用递归来计算数组中所有元素的和。

当数组长度为0时,返回0作为结果。否则,返回最后一个元素加上前n-1个元素的和。

幂运算

给定两个整数x和n,使用递归计算x的n次方。

#include 
using namespace std;int power(int x, int n) {if (n == 0) {return 1;} else if (n % 2 == 0) {int y = power(x, n / 2);return y * y;} else {return x * power(x, n - 1);}
}int main() {int x = 2;int n = 10;cout << power(x, n) << endl;return 0;
}

在这个例子中,power函数接受两个参数:底数x和指数n。函数使用递归来计算x的n次方。

当指数为0时,返回1作为结果。否则,根据指数的奇偶性分别处理。如果指数是偶数,则将其除以2并递归计算x的(n/2)次方,然后将结果平方;如果指数是奇数,则将其减去1并递归计算x的(n-1)次方,然后将结果乘以x。

数组翻转

给定一个整数数组,使用递归将数组翻转

#include 
using namespace std;void reverse(int arr[], int start, int end) {if (start >= end) {return;} else {swap(arr[start], arr[end]);reverse(arr, start + 1, end - 1);}
}int main() {int arr[] = {1, 2, 3, 4};int n = sizeof(arr) / sizeof(arr[0]);reverse(arr, 0, n - 1);for (int i = 0; i < n; i++) {cout << arr[i] << " ";}cout << endl;return 0;
}

在这个例子中,reverse函数接受三个参数:整数数组arr、起始位置start和结束位置end。函数使用递归来将数组翻转。
当起始位置大于等于结束位置时,直接返回。否则,交换两端的元素,并对剩余部分进行递归翻转。

字符串翻转

给定一个字符串,使用递归将字符串翻转。

#include 
#include 
using namespace std;string reverse(string str) {if (str.length() <= 1) {return str;} else {char first = str[0];string rest = str.substr(1);return reverse(rest) + first;}
}int main() {string str = "hello";cout << reverse(str) << endl;return 0;
}

在这个例子中,reverse函数接受一个参数:字符串str。函数使用递归来将字符串翻转。

当字符串长度小于等于1时,直接返回字符串本身作为结果。否则,取出第一个字符和剩余部分,并对剩余部分进行递归翻转,然后将第一个字符添加到末尾。

中级

全排列

给定一个字符串,使用递归打印出该字符串的所有排列。

#include 
#include 
using namespace std;void permute(string str, int l, int r) {if (l == r) {cout << str << endl;} else {for (int i = l; i <= r; i++) {swap(str[l], str[i]);permute(str, l + 1, r);swap(str[l], str[i]);}}
}int main() {string str = "ABC";int n = str.length();permute(str, 0, n - 1);return 0;
}

在这个例子中,permute函数接受三个参数:字符串str、起始位置l和结束位置r。函数使用递归来打印出字符串的所有排列。

当起始位置等于结束位置时,直接打印字符串作为结果。否则,枚举起始位置到结束位置之间的每一个字符,并将其与起始位置的字符交换,然后对剩余部分进行递归排列。

子集

给定一个整数数组,使用递归打印出该数组的所有子集。

#include 
#include 
using namespace std;void subsets(vector &arr, vector> &result, vector &subset, int index) {result.push_back(subset);for (int i = index; i < arr.size(); i++) {subset.push_back(arr[i]);subsets(arr, result, subset, i + 1);subset.pop_back();}
}int main() {vector arr = {1, 2 ,3};vector> result;vector subset;subsets(arr,result ,subset ,0);for(int i=0;icout<<"[";for(int j=0;jcout<

这段代码是一个使用递归生成整数数组所有子集的C++程序。它定义了一个名为subsets的函数,该函数接受四个参数:整数数组arr、结果集合result、当前子集合并subset和当前索引位置index。

在主函数中,首先定义了一个整数数组arr = {1, 2 ,3},然后定义了一个结果集合和一个空的子集合并。接着调用了一次subsets(arr,result ,subset ,0)函数,以生成所有子集。

最后,程序遍历结果集合并打印出每个子集。

高级

正则表达式匹配

给定一个字符串s和一个正则表达式p,使用递归实现正则表达式匹配。

#include 
#include 
using namespace std;bool isMatch(string s, string p) {if (p.empty()) {return s.empty();}bool first_match = (!s.empty() && (s[0] == p[0] || p[0] == '.'));if (p.length() >= 2 && p[1] == '*') {return (isMatch(s, p.substr(2)) || (first_match && isMatch(s.substr(1), p)));} else {return first_match && isMatch(s.substr(1), p.substr(1));}
}int main() {string s = "aa";string p = "a*";cout << isMatch(s, p) << endl;return 0;
}

在这个例子中,isMatch函数接受两个参数:字符串s和正则表达式p。函数使用递归来实现正则表达式匹配。

当正则表达式为空时,如果字符串也为空,则返回true;否则返回false。接着判断第一个字符是否匹配。如果正则表达式的长度大于等于2且第二个字符为’*',那么可以选择忽略这两个字符或者重复第一个字符。否则,继续比较剩余部分。

N皇后问题

给定一个整数n,表示棋盘的大小为n*n,使用递归求出所有不同的n皇后摆放方案。

#include 
#include 
#include 
using namespace std;bool isValid(vector &board, int row, int col) {int n = board.size();for (int i = 0; i < row; i++) {if (board[i][col] == 'Q') {return false;}}for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {if (board[i][j] == 'Q') {return false;}}for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {if (board[i][j] == 'Q') {return false;}}return true;
}void backtrack(vector> &res, vector &board, int row) {if(row==board.size()){res.push_back(board);return ;}int n=board[row].size();for(int col=0;colif(!isValid(board,row,col)){continue;}board[row][col]='Q';backtrack(res,board,row+1);board[row][col]='.';}}vector> solveNQueens(int n) {vector> res;vector board(n,string(n,'.'));backtrack(res,board,0);return res;}int main() {int n=4;vector> result=solveNQueens(n);for(int k=0;kcout<<"Solution "<cout<

这段代码是一个使用递归解决N皇后问题的C++程序。它定义了三个函数:isValid、backtrack和solveNQueens。

其中,isValid函数用于判断在棋盘的第row行第col列放置一个皇后是否合法。它接受三个参数:棋盘board、行号row和列号col。函数通过遍历棋盘上方、右上方和左上方来判断是否有其他皇后与当前位置冲突。

backtrack函数用于回溯搜索所有可能的解。它接受三个参数:结果集合res、棋盘board和当前行号row。当row等于棋盘大小时,表示找到了一种解法,将其加入结果集合中。否则,遍历当前行的每一列,如果在该位置放置皇后合法,则继续搜索下一行。

solveNQueens函数是主要的求解函数。它接受一个整数n作为参数,表示棋盘的大小为n*n。函数首先定义了一个结果集合res和一个空棋盘board,然后调用一次backtrack(res, board, 0)函数以搜索所有可能的解。

在主函数中,首先定义了一个整数n=4,表示棋盘大小为4*4。然后调用solveNQueens(n)函数求解,并打印出所有不同的摆放方案。


总结

🐋 🐬 🐶 🐳 🐰 🦀☝️ ⭐ 👉 👀
递归是一种强大而优雅的编程技巧,在解决复杂问题时非常有用。但是使用时需要注意避免无限循环和栈溢出等问题。

无限循环

避免无限循环是编写递归函数时需要注意的一个重要问题。如果递归函数没有正确地定义基本情况和递归情况,就可能出现无限循环的情况。

为了避免无限循环,我们需要确保每次递归调用都能够使问题的规模减小,最终能够达到基本情况。这样,递归函数就能够在有限的步骤内结束。

例如,在计算阶乘的例子中,我们定义了两种情况:当n等于0时,直接返回1作为结果;否则,返回n乘以(n-1)!。由于每次递归调用都会将n减小1,因此最终一定会达到基本情况n等于0。

另外,在编写递归函数时,也可以使用一些技巧来避免无限循环。例如,可以使用计数器来记录递归调用的次数,并在超过一定次数时强制结束递归。

总之,要避免无限循环,关键是要正确地定义基本情况和递归情况,并确保每次递归调用都能够使问题的规模减小。

栈溢出

栈溢出(stack overflow)是指程序在运行过程中,栈空间被耗尽的情况。这通常是由于程序中存在无限递归或者过深的递归调用导致的。

当程序运行时,每次函数调用都会在栈上分配一定的空间来存储局部变量、参数和返回地址等信息。当函数返回时,这些空间会被释放。如果函数调用层次过深,栈上分配的空间就会超过栈的容量,导致栈溢出。

栈溢出可能会导致程序崩溃或者产生不可预料的行为。因此,在编写递归函数时,应该注意避免无限递归和过深的递归调用。

如果你在解决这些问题时遇到困难,不妨多思考一下,或者寻求其他人的帮助。只要坚持不懈,相信你一定能够掌握递归这一重要技能。


更多宝藏

🍇🍉🍊🍏🍋🍅🥝🥥🫒🫕🥗
项目仓库看这里🤗:
https://github.com/w-x-x-w
https://gitee.com/w-_-x
博客文章看这里🤭:
https://blog.csdn.net/weixin_62650212
视频推送看这里🤤:
https://space.bilibili.com/1909782963

相关内容

热门资讯

单片机stm32新建工程后的编... STM32学习之新建工程模板_stm32工程模板_榕林子的博客-CSDN博客 1、按基本模板新建全新...
【Selenium自动化测试】... JS调用 有些页面操作不能依靠WebDriver 提供的API 来实现,如浏览器滚动条...
苹果笔要不要买原装的?平价又好... 随着科技的不断进步,各种电容笔的生产厂家也随着越来越多。一支优秀的电容笔不仅能大大提高...
3分钟彻底搞懂Web UI自动... 大家好,我是凡哥。 今天,我们来聊聊Web UI自动化测试中的POM设...
[Delphi]一个功能完备的... 本软件使用Delphi 10.3.3编写和测试, 源码中用到了System.NetEncoding和...
java中单例模式的实现 文章目录单例模式前言1.饿汉模式1.1 特点1.2 代码实现2. 懒汉模式2.1 特点2.2 代码实...
Kubernetes集群 服务... Kubernetes集群 服务暴露 Nginx Ingress Controller 一、ingre...
【软件环境安装部署】华为云服务... RabbitMQ 简介 一、什么是RabbitMQ? RabbitMQ简称MQ是一套实...
雪花算法:生成全局唯一 ID ... 提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮...
删除的文件还能找回吗?快速找回...   删除的文件还能找回吗?有使用电脑,就会有删除。可以直接或间接的删除电脑上的照片、视...
算法基础-回溯算法 回溯算法大致分为以下几类: 组合:组合、组合总和、电话号码的字母组合 分...
【Vue3实践】(四)优雅使用... 文章目录1.前言2.属性透传3.依赖注入4.组件插槽(slot)4.1....
【Java学习笔记】41.Ja... 前言 本章介绍Java的文档注释和Java 8 新特性。 Java 文档注释 Java 支持三种注释...
SQL注入之DnsLog注入 一、原理 DnsLog注入并不是一种攻击方式,而是一种让无回显的攻击,变...
【机器学习算法复现】随机森林,... 随机森林就是通过集成学习的Bagging思想将多棵树集成的一种算法:它的基本单元就是决...
sheng的学习笔记-IO多路... 基础概念IO分为几种:同步阻塞的BIO,同步非阻塞的NIO,...
栈----数据结构 栈🔆栈的概念🔆栈的结构🔆栈的实现🔆括...
SpringMVC拦截器和拦截... 文章目录1.拦截器概述2.拦截器和过滤器的区别3.拦截器开发4.拦截器的执行流程5.拦截器链配置1....
springMVC01- 文章目录今日目标一、SpringMVC简介1 SpringMVC概述问题导入1.1 SpringMV...
python基础语法【模块 包... 模块 包 异常捕获 1.模块 python一个py文件就是一个模块 1.1 使用方法 1)前提&#x...