您的位置:首页 > 其它

生成排列(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