基本可以确定的是,我之前肯定是学会过递归的!!而且学会过很多次,可是每次都是学完就忘,过一段时间又不会了😩

一定是递归的错 这次一定好好努力学扎实一些!嗯!

微信图片_20220319124630

# 概述

# 什么是递归

  • 在运行的过程中调用自己
  • 可以理解为 n 叉树
    • 在递归中只调用自己一次 ——1 叉树
    • 在递归中调用自己两次 ——2 叉树
    • 在递归中调用自己 n 次 ——n 叉树

# 构成递归的条件

  1. 可拆分:子问题和原始问题是同样的事情,并且更加简单
  2. 需要有出口:不能无限制地调用本身,必须有出口,化简为非递归状况处理
    • 一般是 if(xxx) return;这个亚子

# 模板

1
2
3
4
5
6
public void recursion(参数0){
if(终止条件){
return;
}
recursion(参数1);
}

# 例题

关于递归的概述其实很简单,就是套娃

用一些例题来加深理解吧~

# 1. 阶乘

1
2
3
4
5
6
public int recursion(int n){
if(n == 1){
return 1;
}
return n * recursion(n - 1);
}

# 2. 斐波那契数列

简单描述一下斐波那契数列就是,第 0 项是 1,第 1 项是 1,从第 2 项开始,每一项都等于前两项之和,很简单的数列,中学数学题就遇到过

1,1,2,3,5,……,(n-1)+(n-2)

1
2
3
4
5
//为了方便理解,这里的n是从1起的
public int fibonacci(int n){
if( n == 1 || n == 2) return 1;
return fibonacci(n - 1) + fibnoacci(n - 2);
}

# 3. 汉诺塔

也是很经典的题目呀~有 3 根柱子 A、B、C,A 柱子【由上到下】依次【从小到大】排列着圆盘。我们的任务是,把 A 柱子上的圆盘全部转移到 C 柱子上,并且在转移的过程中【始终】要保持【任何柱子上】的圆盘都是【由上到下】依次【从小到大】地排列着。

写给未来某一天突然又不知道递归为何物的自己:汉诺塔以及后面的八皇后这两个问题的题目描述看起来有点绕,但是其实仔细读一下就很好理解了,如果连题目描述都读不懂就重开吧!网上有很多别人的帖子将这个问题讲得都蛮好的,配了很多生动形象的插图,如果还是看不懂就搜一下别人的贴子这篇帖子就不画图了没办法谁让我是个懒宝呢

汉诺塔(Hanoi)图解递归算法

勤劳的技术分享博主,谢谢你们!😁😁😁

# 思考步骤
  1. 定义函数

    public void hanoi(int n , char A , char B , char C)

    表示把 n 个圆盘,从 A 柱子,借助 B 柱子,移动到 C 柱子

1
2
3
4
5
6
7
8
9
public void hanoi(int n , char A , char B , char C){
if(n == 1){
System.out.println("从"+A+"移动到"+C+"了喵~");
return;
}
hanoi(n - 1 , A , C , B); //
System.out.println("从"+A+"移动到"+C+"了喵~");
hanoi(n - 1 , B , A , C);
}

为了防止自己的笨蛋脑筋某一天又看不懂了,来一道 leetcode 面试原题 [面试题 08.06. 汉诺塔问题]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//先记录一下最初的错误解法,C++
class Solution {
public:
void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {
if(A.size() == 0){
return;
}
int num = A.size(); //最下面的圆盘的下标
hanoi(num , A , B , C);
}
void hanoi(int n , vector<int>& A, vector<int>& B, vector<int>& C){
if(n == 1) {
C.push_back(A[n - 1]);
return ;
}
hanoi(n - 1 , A , C , B);
C.push_back(A[n - 1]); //问题出在这里欧~!
hanoi(n - 1 , B , A , C);
}
};

//出错了,如下图所示。为什么把A中的元素push_back到C里,答案不对呢?
//仔细想想汉诺塔的移动过程,我们
//1. 首先要把n-1个圆盘【借助C从A移动到B】,此时A柱子上只有1个圆盘(即第n个),B柱子上有n-1个圆盘,C柱子是空的;
//2. 然后把第n个圆盘从A移动到C,此时A柱子是空的,B柱子上还是有n-1个圆盘,C柱子上有1个圆盘(即第n个)
//3. 然后把n-1个圆盘【借助A从B移动到C】。
//需要注意的是,被借助的柱子,初始状态应该是空的才对!也就是说,无论是A还是B或者C柱子,在移动的过程中都需要有【清空】的时候才对。那么!push_back只是取值啊,没有真正的移动,在这个过程中A永远不会是空。
//用remove欧~
//当然了还要注意的一个问题是:自定义函数hanoi里的A、B、C,和题目中指定好的A柱子、B柱子、C柱子是不一样的。hanoi函数的参数A表示的是【源柱子】,参数B表示的是【中间柱子】,参数C表示的是【目标柱子】。所以有的题解会把A叫做from,B叫做process,C叫做to,是蛮清晰的。
//对了。输入样例是【从大到小】的。

微信图片_20220320200916
正确的输入样例应该是 [3,2,1] 才对无语你看看你输入的 [1,2,3] 是什么东西认真一点啊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//正确答案!
class Solution {
public:
void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {
if(A.size() == 0){
return;
}
int num = A.size(); //最下面的圆盘的【下标】
hanoi(num , A , B , C);
}
void hanoi(int n , vector<int>& A, vector<int>& B, vector<int>& C){
if(n == 0) return ;
hanoi(n - 1 , A , C , B);
C.push_back(A.back());//是A.back(),不是A[n-1]啊,因为输入是【从大到小的!!】,最小的在最后面
A.pop_back();
hanoi(n - 1 , B , A , C);
}
};

# 4.N 皇后

N皇后 嗯嗯好酷的名字!

[51. N 皇后]

n 皇后问题

研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

(同一条横线、竖线、斜线上最多只能站一个皇后)

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

微信图片_20220321150503

解决 N 皇后问题,最关键的思路是 回溯 。什么是回溯?

回溯和递归是相辅相成的,回溯的过程通常发生在递归函数之后。简单来讲就是递归加深深度,回溯撤销递归的操作。

什么意思!

拿 N 皇后来举例,当前位置合法(记作 current)之后,就会寻找下一行(row+1)的合法位置,假如没找到合法位置怎么办呢?这说明 current 不是合适的皇后位置,那就回退到 current,并且把此处的 ‘ Q ’ 修改为 ‘ . ’ 。这就是回溯在干的事情欧~

(还有一种情况就是当前搜索路径是完完全全的合法路径,是要存到 result 里最后返回为答案的正确路径,那么在搜索出一条合法路径之后,也是需要一步步回溯到初始状态,进行下一条合法路径的搜索欧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//chessboard的类型是一维string类型的数组:vector<string>,存放单个棋盘合法的摆放情况
//result的类型是二维数组:vector<vector<string>>,存放所有的chessboard
class Solution {
public:
vector<vector<string>> result; //存放结果

void backtracking(int n, int row, vector<string>& chessboard){
if(row == n){ //终止条件:搜索到了最后一行
result.emplace_back(chessboard);
return;
}
for(int col = 0; col < n ; col ++){//按列搜索,需要注意上界:col < n 不能取等!(一开始就是这里WA了
if(isValid(row,col,chessboard,n)){
chessboard[row][col] = 'Q'; //标记为皇后位置(临时的,能不能永存还要看后面的搜索情况)
backtracking(n, row+1, chessboard); //递归
chessboard[row][col] = '.'; //回溯
}
}
}

//检查当前位置是否合法
bool isValid(int row , int col , vector<string>& chessboard , int n){
// 检查同列,上方↑
for(int i = 0 ; i < row ; i ++){
if(chessboard[i][col] == 'Q') return false;
}
// 检查斜线,左上↖
for(int i = row-1 , j = col-1 ; i >= 0 && j >= 0 ; i-- , j--){
if(chessboard[i][j] == 'Q') return false;
}
// 检查斜线,右上↗
for(int i = row -1 , j = col+1 ; i >= 0 && j < n ; i-- , j++){
if(chessboard[i][j] == 'Q') return false;
}
return true;
}

vector<vector<string>> solveNQueens(int n) {
result.clear(); //这句话最好加上
vector<string> chessboard(n, string(n, '.')); //初始化单个棋盘
backtracking(n, 0, chessboard);
return result;
}
};

现在做N皇后这道题,还是蛮有感慨的!因为本科的时候初次进入ACM教室,还记得是一个周六的下午,那天下午在教室里看了一下午这道题,当时很菜(现在依然很菜),看题解都看了半天才好像看懂了一点点,但是仍然记得的是当时那种【恍然大悟】、【哇题解好聪明】、【这道题好有意思】的感觉。

如今我只想说 一切都是最好的安排那就继续烂吧等待命运的安排 继续加油吧!

以后应该会不定期补一些递归的题解进来。

对了!目前还不知道评论功能怎么做,所以假如除了我自己之外还有好兄弟看到我的文章,如果我有写错的地方或者其他的疑问,可以发邮件给我!893766623@qq.com感谢感谢感谢~~~就酱~886