递归问题✨
Recursion
🏳🌈写在前面:
递归是自入门算法以来(虽然也没有很久)碰到的第一个想着想着就蒙了的东西😶,因为看的是算法竞赛书,第一道例题的代码啃了两三天也没搞懂,后来也不知道怎么了,盘着腿叉着腰就突然看懂了(hhh,和姿势无关),下面一些文字就分享一下自己递归入门的过程吧!🙃
定义
正儿八经的百度百科上就有,我在维基百科上看到的一个语言例子把我给看乐了:
⚡一只狗来到厨房,偷走一小块面包。厨子举起杓子,把那只狗打死了。于是所有的狗都跑来了,给那只狗掘了一个坟墓,还在墓碑上刻了墓志铭,让未来的狗可以看到:“一只狗来到厨房,偷走一小块面包。厨子举起杓子,把那只狗打死了。于是所有的狗都跑来了,给那只狗掘了一个坟墓,还在墓碑上刻了墓志铭,让未来的狗可以看到……”
但是作为计算机算法,需要满足有穷性,因此写递归的时候需要加上停止条件。
例题:递归打印1,2,3,4,5的全排列
(把我绕晕的例题,也是很经典的一个例题)
分析
依愚拙见,看递归代码尽量不要加断点一步步调,我一开始这么干是真的晕(也可能是我菜),不过依照某大佬的建议,画调用数,先用1,2,3排列做试了一下,但这是在我已经弄懂的情况下再做,虽然没那么蒙圈,但我觉得还是有点绕。我的思路如下:
⚡第一步:明白递归的主体思路
⚡第二步:明白递归需在何时停止
⚡第三步:明白下一次递归(即问题往小了化解)时的条件
👍知道了这三步,就基本完成了对一个递归程序的构造
接下来只需要套框架:(部分代码依据题目难度省略或添加)
int perm(形参1){
if(结束条件){
结束前想做的,如这题是打印出全排列
return 0;
}
else{
主体代码块,其中包含:
perm(形参2); //这里的形参一定是能将大问题分解为小问题的参数
}
return 0;
}
本题代码及解读:
#include<bits/stdc++.h>
using namespace std;
#define Swap(x,y) {int t=x;x=y;y=t;} //也可使用C++STL库中的swap函数,但效率较低
int a[]={1,2,3,4,5}; //要排列的五个数
int Perm(int begin,int end){
if(begin==end){ //停止条件
for(int i=0;i<5;i++)
printf("%d ",a[i]);
cout<<endl;
}
else{
for(int i=begin;i<end;i++){
Swap(a[begin],a[i]);
Perm(begin+1,end);
Swap(a[begin],a[i]);
}
}
return 0;
}
int main(){
Perm(0,5);
return 0;
}
比较难懂的应该就是else语句里的for循环部分了,但是不要一步步把数字带进去看。因为我们明白了程序是在begin=end时结束,所以只需弄清楚大问题如何转化为小问题就行了,而这里恰恰就是问题的关键:
易知两句Swap语句之后排列不变,中间的Perm只是将begin+1送入下一个小问题,所以大问题就是perm(begin,end),小问题就是perm(begin+1,end),而他们之间的转化关系就是Swap(a[begin],a[i]),而此处
i
∈
[
b
e
g
i
n
,
e
n
d
]
i\in[begin,end]
i∈[begin,end],所以说这道题的大问题会转化为多个小问题。
这样我们就清楚了整个问题的内部逻辑,用图来表示就是:
这里只列出了一部分,但应该是能很好地理解整个大问题到小问题的分解,因此整个问题迎刃而解。