递归与递推是最基础也是最常用的算法,在很多地方都能见到他们的影子,下面就让我来带领大家一起学习一下。本篇文章讲究实用,会以习题的方式进行展开。废话不多说,我们开始吧!
1.递归实现指数型枚举
从 1∼n 这 n 个整数中随机选取任意多个,输出所有可能的选择方案。
输入格式
输入一个整数 n。
输出格式
每行输出一种方案。
同一行内的数必须升序排列,相邻两个数用恰好 1 个空格隔开。
对于没有选任何数的方案,输出空行。
本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。
数据范围
1≤n≤15
输入样例:
3
输出样例:
3
2
2 3
1
1 3
1 2
1 2 3
解题思路:
首先要知道,dfs()里的参数是从1~n,传递到当前第几位。这样也不行,我们想要直到每个位置是否已经或者还没有枚举过,因此要开一个数组记st[]录每个位置当前的状态。
代码:
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N=20; int st[N];//状态数组,1表示选择当前的数,2表示不选,0表示还没枚举到 int n; void dfs(int u) { if(u>n)//递归都要有一个递归边界,否则将永远递归下去,这里的边界是枚举完所有的点 { for(int i=1;i<=n;i++) if(st[i]==1) cout<<i<<" "; cout<<endl; return ; } st[u]=2;//1.不选当前的数,即不选u dfs(u+1);//递归进入下一个数 st[u]=0;//恢复现场 st[u]=1;//2、选择当前的数,即选择u dfs(u+1);//递归进入下一个数 st[u]=0;//恢复现场 } int main(){ cin>>n; dfs(1);//传递到当前第1个数 return 0; }
2.递归实现排列型枚举
把 1∼n 这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序。
输入格式
一个整数 n。
输出格式
按照从小到大的顺序输出所有方案,每行 1 个。
首先,同一行相邻两个数用一个空格隔开。
其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面。
数据范围
1≤n≤9
输入样例:
3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
解题思路:
首先要知道,dfs()里的参数是从1~n,当前应该传递第几位。确定递归边界:当把n个数全部枚举完之后,输出一个答案,这里记录答案的是st[]数组,里面存的是第1 ~n位选择的数。另外,还要开一个数组used[]表示当前数是否已经选择了
代码:
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N=10; int st[N]; bool used[N]; int n; void dfs(int u) { if(u>n)//递归都要有一个递归边界,否则将永远递归下去,这里的边界是枚举完所有的点 { for(int i=1;i<=n;i++) cout<<st[i]<<" ";//st[]数组里存放着一行输出 cout<<endl; return ; } for(int i=1;i<=n;i++) { if(!used[i]) { st[u]=i; used[i]=true; dfs(u+1); st[u]=0;//恢复现场 used[i]=false; } } } int main(){ cin>>n; dfs(1); return 0; }
3.递归实现组合型枚举
从 1~n 这 n 个整数中随机选出 m 个,输出所有可能的选择方案。
输入格式
两个整数 n,m ,在同一行用空格隔开。
输出格式
按照从小到大的顺序输出所有方案,每行 1 个。
首先,同一行内的数升序排列,相邻两个数用一个空格隔开。
其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面(例如 1 3 5 7 排在 1 3 6 8 前面)。
数据范围
n>0 ,
0≤m≤n ,
n+(n−m)≤25
输入样例:
5 3
输出样例:
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5
思考题:如果要求使用非递归方法,该怎么做呢?
解题思路:在排列问题中,是在n个数中选择m个数,题目中要求是按找字典序输出,因此在选择每一个数时,每次只要选择比某个数大1的数,再递归下去,即可按字典序输出,在DFS函数中,一共有两个参数,第一个参数表示当前应该选择第几个数(一共m个),第二个参数表示第一个比当前数大的数。在此过程中用st[]数组存储每一组组合数
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N=30; int st[N]; int n,m; void dfs(int u,int start) { if(u>m)//当前已经选择完所有数了 { for(int i=1;i<=m;i++) cout<<st[i]<<" "; cout<<endl; return ; } for(int i=start;i<=n;i++) { st[u]=i; dfs(u+1,i+1); st[u]=0;//恢复原场 } } int main(){ cin>>n>>m; dfs(1,1); return 0; }
4.递归实现排列类型枚举 II
给定一个长度为 n 的可包含重复数字的序列,请你求出其所有不重复的全排列。
输入格式
第一行包含整数 n。
第二行包含 n 个整数。
输出格式
输出所有的不同排列,每种排列占一行。
在确定每种排列的输出顺序时,第一个数较小的先输出,第一个数相同时,第二个数较小的先输出,以此类推。
数据范围
1≤n≤9,
数组中包含的元素的取值范围 [1,9]
输入样例:
3
1 1 2
输出样例:
1 1 2
1 2 1
2 1 1
解题思路:
和递归实现组合型枚举I思路相似,都是向dfs中传递参数u,表示当前应该传递第几个数(还没选择),只不过这里要再开一个数组value[]存储输入 的数
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N=12; int st[N],value[N];//st存储每一组要输出的全排列 bool used[N]; int n; void dfs(int u) { if(u>n)//当前已经选好所有的数 { for(int i=1;i<=n;i++)cout<<st[i]<<" "; cout<<endl; return ; } for(int i=1;i<=n;i++) { if(!used[i]) { st[u]=value[i]; used[i]=true; dfs(u+1); used[i]=false; while(i+1<=n&&value[i]==value[i+1]) i++;//去重:去除排序后相邻的相同元素 } } } int main(){ cin>>n; for(int i=1;i<=n;i++) cin>>value[i]; sort(value+1,value+1+n); dfs(1); return 0; }
5.递归实现指数型枚举 II
给定一个长度为 n 的可包含重复数字的序列,从中随机选取任意多个数字,输出所有可能的选择方案。
输入格式
第一行包含一个整数 n,表示序列长度。
第二行包含 n 个正整数。
输出格式
每行输出一种方案。
同一行内的数必须升序排列,相邻两个数用恰好1个空格隔开。
对于没有选任何数的方案,输出空行。
本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。
数据范围
1≤n≤15,
序列内所有元素均不大于 n。
输入样例:
3
1 2 2
输出样例:
1
2
1 2
2 2
1 2 2
解题思路:dfs函数传递的是当前要选择第u个数字,当u>n说明已经枚举完所有的数,可以输出了
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N=20; int st[N]; bool used[N];//标记是否选过 int n; void dfs(int u) { if(u>n) { for(int i=1;i<=n;i++) if(used[i]) cout<<st[i]<<" "; cout<<endl; return ; } dfs(u+1); //防止出现重复情况 if(u!=1&&!used[u-1]&&st[u]==st[u-1]) return; used[u]=true; dfs(u+1); used[u]=false; } int main(){ cin>>n; for(int i=1;i<=n;i++) cin>>st[i]; sort(st+1,st+n+1);//排序方便计算重复数字 dfs(1); return 0; }
6.递归实现组合型枚举 II
给定一个长度为 n 的可包含重复数字的序列,从中随机选取 m 个数字,输出所有可能的选择方案。
输入格式
第一行包含两个整数 n,m。
第二行包含 n 个正整数。
输出格式
按照从小到大的顺序输出所有方案,每行 1 个。
首先,同一行内的数升序排列,相邻两个数用一个空格隔开。
其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面(例如1 3 5 7排在1 3 6 8前面)。
数据范围
n>0,
0≤m≤n,
n+(n−m)≤25,
序列内所有元素均不大于 n。
输入样例:
5 3
1 2 2 3 3
输出样例:
1 2 2
1 2 3
1 3 3
2 2 3
2 3 3
解题思路:dfs里传入两个参数,第一个是要选择第u个数(一共m个数),第二个数是当前要选择st数组中下标为几的数,当u>m时返回一组答案,否则,从当前下标开始到n,若下标为start,且前一个数没有选择,且前一个数与之相等,则跳过,开始下一个数。直到递归到n
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N=30; int st[N];//存储输入值以及要输出的组合数 bool used[N];//记录是否已经使用过 int n,m; void dfs(int u,int start) { if(u>m) { for(int i=1;i<=n;i++) if(used[i]) cout<<st[i]<<" ";//若当前这个数没有选择则输出 cout<<endl; return; } for(int i=start;i<=n;i++) { if(i!=1&&!used[i-1]&&st[i-1]==st[i])//若不选第一个数,且前一个数没有选择,且前一个数与之相等,则跳过 continue; used[i]=true; dfs(u+1,i+1); used[i]=false; } } int main(){ cin>>n>>m; for(int i=1;i<=n;i++) cin>>st[i]; sort(st+1,st+n+1);//记得排序!! dfs(1,1);//当前要选择第1个数,从st[1]开始选择 return 0; }
大家可能会有这样的一种疑问:为什么本章例题中调用的函数名全部都是dfs?
因为dfs是递归的一种,大家发没发现这六个例题中都有要选择几个数,每个数都有几种选法,这两个概念可以抽象为dfs深度搜索树的树深以及节点的分支。掌握这个概念相信大家就会对递归有了初步的认识,接下来我会推出实际应用型的递归。喜欢这篇文章的话点一波关注不迷路~ 后续会继续推出我的算法学习心得哦