zoukankan      html  css  js  c++  java
  • 全排列

    生成1~n的排列

           我们尝试用递归的思想解决:先输出所有以1开头的排列(这一步是递归调用),然后 输出以2开头的排列(又是递归调用),接着是以3开头的排列……最后才是以n开头的排 列。
            以1开头的排列的特点是:第一位是1,后面是2~9的排列。根据字典序的定义,这些2 ~9的排列也必须按照字典序排列。换句话说,需要“按照字典序输出2~9的排列”,不过需 注意的是,在输出时,每个排列的最前面要加上“1”。这样一来,所设计的递归函数需要以 下参数:
    已经确定的“前缀”序列,以便输出。 需要进行全排列的元素集合,以便依次选做第一个元素。
    这样可得到一个伪代码:

    void print_permutation(序列A, 集合S)
    {
        if(S为空) 输出序列A;
         else 按照从小到大的顺序依次考虑S的每个元素v
          {
           print_permutation(在A的末尾填加v后得到的新序列, S-{v});
       }
    }      

           暂时不用考虑序列A和集合S如何表示,首先理解一下上面的伪代码。递归边界是S为空 的情形,这很好理解:现在序列A就是一个完整的排列,直接输出即可。接下来按照从小到大的顺序考虑S中的每个元素,每次递归调用以A开头。

            下面考虑程序实现。不难想到用数组表示序列A,而集合S根本不用保存,因为它可以 由序列A完全确定——A中没有出现的元素都可以选。C语言中的函数在接受数组参数时无法 得知数组的元素个数,所以需要传一个已经填好的位置个数,或者当前需要确定的元素位置 cur,代码如下:

    #include<iostream>
    using namespace std;
    const int maxn=10000;
    int a[maxn];
    void print_permutation(int n,int a[],int cur)
    {
        if(cur==n)     //递归边界 
        {
            for(int i=0;i<n;i++) cout<<a[i];
            cout<<endl;
        }
        else
        {
            for(int i=1;i<=n;i++)   //尝试a[cur]中从小到大填1-n各个整数 
            {
                int ok=1;    //标志元素i是否已经用过 
                for(int j=0;j<cur;j++)
                if(a[j]==i) ok=0;  //i已经用过,ok标记为0 
                if(ok)
                {
                    a[cur]=i;   //i未用过,把它添加到序列末尾后继续递归调用 
                    print_permutation(n,a,cur+1);
                }
            }
        }
    }
    int main()
    {
        int n;
        cin>>n;
        print_permutation(n,a,0);
        return 0;
    }

         循环变量i是当前考察的A[cur]。为了检查元素i是否已经用过,上面的程序用到了一个 标志变量ok,初始值为1(真),如果发现有某个A[j]==i时,则改为0(假)。如果最终ok仍 为1,则说明i没有在序列中出现过,把它添加到序列末尾(A[cur]=i)后递归调用。
         声明一个足够大的数组A,然后调用print_permutation(n, A, 0),即可按字典序输出1~n的 所有排列。

     给定数组的全排列

           如果把问题改成:输入数组P,并按字典序输出数组A各元素的所有全排列,则需要对 上述程序进行修改——把P加到print_permutation的参数列表中,然后把代码中的if(A[j] == i) 和A[cur] = i分别改成if(A[j] == P[i])和A[cur] = P[i]。这样,只要把P的所有元素按从小到大的
    顺序排序,然后调用print_permutation(n,  A, 0,p)即可。(注意:此方法数组内元素不可重复)

    #include<iostream>
    #include<algorithm>
    using namespace std;
    const int maxn=10000;
    int a[maxn];
    void print_permutation(int n,int a[],int cur,int p[])
    {
        if(cur==n)     //递归边界 
        {
            for(int i=0;i<n;i++) cout<<a[i];
            cout<<endl;
        }
        else
        {
            for(int i=0;i<n;i++)   //尝试a[cur]中从小到大填p[i]中的各个整数 
            {
                int ok=1;    //标志元素i是否已经用过 
                for(int j=0;j<cur;j++)
                if(a[j]==p[i]) ok=0;  //p[i]已经用过,ok标记为0 
                if(ok)
                {
                    a[cur]=p[i];   //p[i]未用过,把它添加到序列末尾后继续递归调用 
                    print_permutation(n,a,cur+1,p);
                }
            }
        }
    }
    int main()
    {
        int p[20];
        int n;
        cin>>n;
        for(int i=0;i<n;i++)
            cin>>p[i];
        sort(p,p+n);
        print_permutation(n,a,0,p);
        return 0;
    }

    生成可重集的排列

    方法一:

          一个解决方法是统计A[0]~A[cur-1]中P[i]的出现次数c1,以及P数组中P[i]的出现次数 c2。只要c1<c2,就能递归调用。结果又如何呢?输入1 1 1,输出了27个1 1 1。遗漏没有了,但是出现了重复:先试着把 第1个1作为开头,递归调用结束后再尝试用第2个1作为开头,递归调用结束后再尝试用第3 个1作为开头,再一次递归调用。可实际上这3个1是相同的,应只递归1次,而不是3次。
           换句话说,我们枚举的下标i应不重复、不遗漏地取遍所有P[i]值。由于P数组已经排过 序,所以只需检查P的第一个元素和所有“与前一个元素不相同”的元素,即只需在“for(i = 0; i < n; i++)”和其后的花括号之前加上“if(!i || P[i] != P[i-1])”即可。

    #include<iostream>
    #include<algorithm>
    using namespace std;
    const int maxn=10000;
    int a[maxn];
    void print_permutation(int n,int a[],int cur,int p[])
    {
        if(cur==n)     //递归边界 
        {
            for(int i=0;i<n;i++) cout<<a[i];
            cout<<endl;
        }
        else
        {
            for(int i=0;i<n;i++)   //尝试a[cur]中从小到大填p[i]中的各个整数 
            {
                if(p[i]!=p[i-1])
                {
                int c1=0,c2=0;
                for(int j=0;j<cur;j++) if(a[j]==p[i]) c1++;
                for(int j=0;j<n;j++)   if(p[j]==p[i]) c2++;
                if(c1<c2)
                {
                    a[cur]=p[i];
                    print_permutation(n,a,cur+1,p);
                }
            }
            }
        }
    }
    int main()
    {
        int p[20];
        int n;
        cin>>n;
        for(int i=0;i<n;i++)
            cin>>p[i];
        sort(p,p+n);
        print_permutation(n,a,0,p);
        return 0;
    }

     方法二:

          枚举所有排列的另一个方法是从字典序最小排列开始,不停调用“求下一个排列”的过 程。如何求下一个排列呢?C++的STL中提供了一个库函数next_permutation。看看下面的代 码片段,就会明白如何使用它了。

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int main( ) {
        int n, p[10];
        scanf("%d", &n);
        for(int i = 0; i < n; i++) scanf("%d", &p[i]);
        sort(p, p+n);      //排序,得到p的最小排列 
        do {
            for(int i = 0; i < n; i++) printf("%d ", p[i]);
            printf("
    ");
        } while(next_permutation(p, p+n)); 
        return 0;
    }
        
        

    需要注意的是,上述代码同样适用于可重集。

  • 相关阅读:
    BeanFactory 简介以及它 和FactoryBean的区别
    由kill 和 kill -9 引发的Linux signal 学习
    验证整数和小数的正则表达式
    重构!重构!重构!
    Java常用命令:jps、jstack、jmap、jstat(带有实例教程)
    子网掩码是4个255代表什么?
    常见的访问控制模型 Access Control Policy:RBAC,DAC,MAC,ABAC
    安装驱动
    大话数据治理-01什么是治理,治理什么数据
    提高 nginx 服务器 安全性,稳定性、性能 --经验总结-持续更新
  • 原文地址:https://www.cnblogs.com/zjl192628928/p/9294426.html
Copyright © 2011-2022 走看看