生成排列(DFS)
来源:互联网 发布:淘宝买家信用贷款 编辑:程序博客网 时间:2024/06/14 14:39
模板
void dfs(int x){ if(到达目的地) { if(解合法) 输出解; return; } for(枚举选择个数) { if(合法) { 保存结果; 更改状态; dfs(下一步); 回溯; } }}
例题
例1
生成k的n维向量(1<=k<=10,1<=n<=6)
n维向量是有n个元素的序对,每数的取值范围从1到k
输入k和n,输出所有k的n维向量
譬如2的3维向量
有{1,1,1}{1,1,2}{1,2,1}{1,2,2}{2,1,1}{2,1,2}{2,2,1}{2,2,2}
譬如3的5维向量
有{1,1,1,1,1}{1,1,1,1,2}{1,1,1,1,3}{1,1,1,2,1}······{3,3,3,3,2}{3,3,3,3,3}
分析
可以把每一次构造的分成n个阶段
一阶段:从k个数中选一个数出来
二阶段:从k个数中选一个数出来
三阶段:从k个数中选一个数出来
·············
n阶段:从k个数中选一个数出来
n个阶段后,就输出解
代码
#include<cstdio>#define M 5int ans[M+5],n,k;void dfs(int x){ if(x>n)//判断是否可以输出解 { for(int i=1;i<n;i++) printf("%d ",ans[i]); printf("%d\n",ans[n]); return; } for(int i=1;i<=k;i++) { ans[x]=i;//保存结果 dfs(x+1);//进入下一阶段 }}int main(){ scanf("%d%d",&k,&n); dfs(1); return 0;}
例2
如果在例1的基础上,在每个方案前加上序号,又该怎么写?
分析
只用加上计数器就可以了,输出序号后,让序号加1;
代码实现如下
#include<cstdio>#define M 5int ans[M+5],n,k,cnt;void dfs(int x){ if(x>n) { cnt++;//自加 printf("%d:",cnt);//输出序号 for(int i=1;i<n;i++) printf("%d ",ans[i]); printf("%d\n",ans[n]); return; } for(int i=1;i<=k;i++) { ans[x]=i; dfs(x+1); }}int main(){ scanf("%d%d",&k,&n); dfs(1); return 0;}
例3
如果将向量改成1到n的全排列呢?
输入n,输出1到n的全排列
分析
只需加一个check,搜锁即将放的数在之前有没有放过。
代码
#include<cstdio>#define M 5int ans[M+5],n,cnt;bool check(int v,int u)//查找函数{ for(int i=1;i<=u;i++) if(ans[i]==v)return 0;//如果一样,返回0 return 1;}void dfs(int x){ if(x>n) { printf("%d:",++cnt); for(int i=1;i<n;i++) printf("%d ",ans[i]); printf("%d\n",ans[n]); return; } for(int i=1;i<=n;i++) if(check(i,x)) { ans[x]=i; dfs(x+1); }}int main(){ scanf("%d",&n); dfs(1); return 0;}
大家可以试试输入7会发生什么,8呢?9呢?
显然程序“变慢”了,那么该如何解决呢
其实,将用过的数标记就可以了,但回溯时一定改回来
代码如下
#include<cstdio>#define M 5int ans[M+5],n,cnt;bool vis[M+5];void dfs(int x){ if(x>n) { printf("%d:",++cnt); for(int i=1;i<n;i++) printf("%d ",ans[i]); printf("%d\n",ans[n]); return; } for(int i=1;i<=n;i++) if(!vis[i])//如果没使用过 { vis[i]=true;//标记使用过 ans[x]=i; dfs(x+1); vis[i]=false;//记得回溯 }}int main(){ scanf("%d",&n); dfs(1); return 0;}
时间肯定快了,但仍然看不出效果,接下来介绍一种最快的算法。
大家发现,每次只用交换两数的位置就可以生成新排列。
交换时注意交换过的不要再交换。所以从当前开始往后交换。
代码实现如下
#include<cstdio>#include<algorithm>using namespace std;#define M 10int n,cnt,ans[M+5]={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14};//先把数按从1到n存进去void dfs(int x){ if(x>n) { printf("%d:",++cnt); for(int i=1;i<n;i++) printf("%d ",ans[i]); printf("%d\n",ans[n]); return; } for(int i=x;i<=n;i++)//与后面的数交换,前面的数交换过 { swap(ans[x],ans[i]);//交换这两个数 dfs(x+1); swap(ans[x],ans[i]);//一定记得交换回来 }}int main(){ scanf("%d",&n); dfs(1); return 0;}
这里提一下,这种算法没有按字典序排,如果题目要求字典序输出,就用第二种。
如例4就需要字典序
例4
输入一个包含 n个非负数的数组,元素可以重复。按字典序输出所有全排列方案,要求不重复。
(1<=n<=10)
看起来复杂,一会重复一会不重复的,但跟前面差不多,只需多定义一个变量las,用来储存当层函数上一次填的数,如果一样,显然重复,就继续往后循环,但别忘了更改las的值
代码如下
#include<cstdio>#define M 10int ans[M+5],n,cnt,a[M+5];bool vis[M+5];void dfs(int x){ if(x>n) { printf("%d:",++cnt); for(int i=1;i<n;i++) printf("%d ",ans[i]); printf("%d\n",ans[n]); return; } int las=-1;//输入非负数,所以las初值为负数 for(int i=1;i<=n;i++)//枚举n个数 if(!vis[i]&&las!=a[i]) { vis[i]=true; ans[x]=a[i]; las=a[i];//更改las的值 dfs(x+1); vis[i]=false; }}int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); dfs(1); return 0;}
另一种方法针对每个数范围小
将数字的种类表示出来,这种方法对空间需求较高
代码如下
#include<cstdio>#define M 10#define N 50int ans[M+5],n,cnt,a[M+5];int c[N+5];//假设每个数不超过50,存每种数可以使用的次数void dfs(int x){ if(x>n) { printf("%d:",++cnt); for(int i=1;i<n;i++) printf("%d ",ans[i]); printf("%d\n",ans[n]); return; } for(int i=0;i<=N;i++)//枚举数的种类 if(c[i]) { c[i]--;//使用数 ans[x]=i; dfs(x+1); c[i]++;//回收数 }}int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); c[a[i]]++;//初始化 } dfs(1); return 0;}
例5
STL标准库中的next_permutation()函数可以生成下一个排列,结合循环就可以将从当前排列开始的所有排列生成出来。所以,使用前往往会先排序,且一般把它放到while()循环当中。(需要头文件algorithm)
代码如下
#include<cstdio>#include<algorithm>using namespace std;//调用STL函数#define M 10int a[M],cnt;int main(){ int n; scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); sort(a+1,a+1+n);//生成最小排列(从小到大排序) do { printf("%d:",++cnt); for(int i=1;i<n;i++) printf("%d ",a[i]); printf("%d\n",a[n]); }while(next_permutation(a+1,a+1+n)); return 0;}
- 生成排列(DFS)
- dfs 生成排列和组合
- 排列数的生成(DFS)
- DFS生成n个数的排列数
- 生成全排列(STL、dfs)
- ZOJ Problem Set - 3861 ( DFS + 子集生成 + 有条件的全排列生成 )
- 生成排列
- 生成排列
- 生成排列
- 生成排列
- 全排列 DFS实现
- 全排列 DFS实现
- dfs全排列
- DFS求全排列
- DFS实现全排列
- DFS之全排列
- uva10344(全排列+dfs)
- DFS之全排列
- P2P贷款全攻略,贷前、贷中、贷后工作事项解析
- DFS模板
- 教程篇(5.4) NSE4 13. 虚拟域 ❀ 飞塔 (Fortinet) 网络安全专家
- 购物车死数据
- 简单实现SQLite+断点续传
- 生成排列(DFS)
- hdu3488-Tour(最大权匹配变种)
- Linux进程
- 多线程(一)
- FTP主动模式与被动模式
- 【Python】Matplotlib画图(七)——线的颜色、点的形状
- mysqldump导出时2013问题
- EasyRecyclerView,简单易上手,支持多种常用的功能的RecyclerView
- 初识SQL中in和exists关键字