全排列及几种实现(DFS,STL)

全排列

从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列。当m=n时所有的排列情况叫全排列

公式:全排列数f(n)=n!(定义0!=1)

例如1,2,3的全排列为共有3!=6种,按字典序从小到大为:

1,2,3

1,3,2

2,1,3

2,3,1

3,1,2

3,2,1

DFS

DFS求全排列的思想是:把n个数建立成一个含有n个节点的无向图,那么我们对每一个节点跑一次深搜,就能得到一个由该起点开始的全排列,那么一个for循环就会得到所有的全排列

在这里插入图片描述

如上图,实际上是构建了一个全排列的森林,从上到下分别是第一层dfs循环,第二层dfs循环,第三层dfs循环。显然当我们递归到第四层时就需要return,并且每一层执行完dfs需要清空标记数组,这样才能继续递归。当然也可以像二叉树的DFS那样理解,只是我们实现的是线性数据结构的dfs

在这里插入图片描述

值得注意的是,对输入序列进行排序后,最后得到的全排列也会是按字典序依次输出

const int maxn=?;
int a[maxn],ans[maxn];     //存输入的数组
bool vis[maxn];
int n;    //当前序列长度
int cnt;  //记录全排列种数

void dfs(int x){    //x表示当前已得到序列的长度
    if(x==n+1){
    //如果第n+1次仍然进入该循环那么就表示之前的n次存储了一次全排列,输出返回
        for(int i=1;i<=n;i++) printf("%d%c",ans[i],i==n?'\n':' ');
        cnt++;
        return;
    }
    for(int i=1;i<=n;i++){
        if(!vis[i]){
            ans[x]=a[i];  //赋值到ans数组
            vis[i]=1;
            dfs(x+1);
            vis[i]=0;     //重要操作
        }
    }   
}

参考过紫书之后,发现也可以不设置vis数组,而是统计每次ans数组里是否含有当前的a[i]即可

int a[maxn],ans[maxn];
int n,cnt;

void print_permutation(int cur){
    if(cur==n+1){
        for(int i=1;i<=n;i++) printf("%d%c",ans[i],i==n?'\n':' ');
        cnt++;   //统计排列数量
        return;
    }
    for(int i=1;i<=n;i++){
        int ok=1;
        for(int j=1;j<=cur;j++)
            if(ans[j]==a[i]) ok=0;  //如果当前a[i]已经进入ans数组则不需生成下面的排列
        if(ok){
            ans[cur]=a[i];
            print_permutation(cur+1);
        }
    }
}

生成可重集的全排列

通过观察我们发现上面生成的只能是不可重集的全排列,即各种数字不能相同,如果我们输入1 1 1的话不会得到期望的结果,实际上我们只需要一个即可,那我们可以这样改进:用map统计一下当前排列是否出现过即可,但是这样只能用dfs的方法,上面紫书的思路仍然没有输出

map<string,int> mp;  //每次使用过后注意清空
int a[maxn],ans[maxn];
bool vis[maxn];
int n,cnt;


void dfs(int x){
    if(x==n+1){
        string s="";
        for(int i=1;i<=n;i++) s+=ans[i]+'0';
        if(!mp[s]){
            mp[s]=1;
            cout<<s<<endl;
            cnt++;
        }
        return;
    }
    for(int i=1;i<=n;i++){
        if(!vis[i]){
            ans[x]=a[i];
            vis[i]=1;
            dfs(x+1);
            vis[i]=0;
        }
    }
}
STL内置函数

PS:既适用于不可重集也适用于可重集

next_permutation()

next_permutation(a,a+length)函数是按照字典序产生排列的,并且是从数组中当前的字典序开始依次增大直至到最大字典序。也就是说如果刚开始是升序,会一步一步排列直至降序

当排列已经是最大,返回假,否则真

int main()
{
    int a[5]= {1,2,3,4};
    do{
        for(int i=0; i<4; i++)
            cout<<a[i]<<" ";
        cout<<endl;
    }while(next_permutation(a,a+4));
    return 0;
}

prev_permutation()

prev_permutation(a+length)函数生成给定序列的上一个较小的排列,顺序是从大到小。
当排列已经是最小,返回假,否则真

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值