全排列

生成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;
}
    
    

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

原文地址:https://www.cnblogs.com/zjl192628928/p/9294426.html