生成排列(DFS)
2017-11-24 14:04
253 查看
模板
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 5 int 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 ); 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 5 int 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 ); 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 5 int 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 ); 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 5 int 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 ); return; } for(int i=1;i<=n;i++) if(!vis[i])//如果没使用过 { vis[i]=true;//标记使用过 ans[x]=i; dfs(x+1); vis[i]=false< 4000 /span>;//记得回溯 } } int main() { scanf("%d",&n); dfs(1); return 0; }
时间肯定快了,但仍然看不出效果,接下来介绍一种最快的算法。
大家发现,每次只用交换两数的位置就可以生成新排列。
交换时注意交换过的不要再交换。所以从当前开始往后交换。
代码实现如下
#include<cstdio> #include<algorithm> using namespace std; #define M 10 int 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 ); 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 10 int 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 ); 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 50 int 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 ); 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 10 int 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 ); }while(next_permutation(a+1,a+1+n)); return 0; }
相关文章推荐
- dfs 生成排列和组合
- 【NEUQ】 H Lethe的手环 【圆周排列生成】or【DFS 剪枝】
- dfs生成排列组合模板
- dfs 生成排列和组合
- dfs 生成排列和组合
- 生成组合和排列
- 生成n个数的排列的算法
- HDU 5723 Abandoned country (最小生成树+dfs)
- 按字典序生成排列的算法(深度优先搜索)
- 度限制最小生成树 POJ 1639 贪心+DFS+prim
- hdu 1716 排列2(DFS搜索)
- 生成排列的非递归实现算法
- 换位法生成排列程序
- usaco1.3.5(dfs生成配对情况)
- 图的深度优先搜索(DFS),广度优先搜索(BFS)与最小生成树(MST)
- bzoj1016: [JSOI2008]最小生成树计数(kruskal+dfs)
- 生成1-n的全排列...
- 学以致用-利用Excel函数生成任意两列数值的排列(可轻松扩展为任意多列值的排列)
- 生成排列列
- HDOJ-1501 字符排列问题[字符搜索DFS()]