生成排列(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;}
原创粉丝点击