暴力求解法2

7.2枚举排列
有没有想过如何打印所有排列呢?输入整数n,按字典序从小到大的顺序输出前n个数的
所有排列。前面讲过,两个序列的字典序大小关系等价于从头开始第一个不相同位置处的大
小关系。例如,(1,3,2) < (2,1,3),字典序最小的排列是(1, 2, 3, 4,…, n),最大的排列是(n, n-1,
n-2,…, 1)。n=3时,所有排列的排序结果是(1, 2, 3)、(1, 3, 2)、(2, 1, 3)、(2, 3, 1)、(3, 1, 2)、
(3, 2, 1)。
7.2.1 生成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,代码如下:


 
 
1到N的全排列主要在递归部分,递归边界比较容易理解
首先将数组A赋予第一次排列得值,1到N,此过程是在递归中完成的,然后在向前求解递归的值 即可得答案。
第一个大循环给出1到N的数值并且不断向数组中添,完成排列。
中间一个循环一个判断
循环是用来判断是否在排列中然后加到数组中
判断即完成递归
(重点是第二次递归   递归cur为5时判断 A[5]=7,在到cur=6 边A[6]=6)
#include <iostream>
#include<cstdio>
using namespace std;
void print_permutation(int n,int *A,int cur)
{
    if(cur==n)//递归边界
    {
        for(int i=0; i<n; i++)
            printf("%d",A[i]);
        printf("\n");
    }
    else
        for(int i=1; i<=n; i++)
        {
            int ok=1;
            for(int j=0; j<cur; j++)
                if(A[j]==i)
                    ok=0;
            if(ok)//尝试在A[cur]中填各种整数i,如果i在A[0]到A[cur]中出现过则不能再选
            {
                A[cur]=i;
                print_permutation(n,A,cur+1);
            }
        }
}
int main()
{
    int n;
    int A[10];
    for (int i = 0; i < 10; i++)
        A[i]=0;
    while (scanf("%d",&n) == 1)
        print_permutation(n,A,0);
    return 0;
}
7.2.2 生成可重集的排列
如果把问题改成:输入数组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, P, A, 0)即可。
这个方法看上去不错,可惜有一个小问题:输入1 1 1后,程序什么也不输出(正确答案
应该是唯一的全排列1 1 1),原因在于,这样禁止A数组中出现重复,而在P中本来就有重
复元素时,这个“禁令”是错误的。
一个解决方法是统计A[0]~A[cur-1]中P[i]的出现次数c1,以及P数组中P[i]的出现次数
c2。只要c1<c2,就能递归调用。
与前面的不重复全排列大致一样 区别在于有重复元素
else 中的代码第一个大循环是基础(对于排列来说,各个元素循环递归都在其中)
 if判断 全排列是由字典序从小到大,而排列恰好是从后向前如果两个紧挨着元素大小一样即是不用排列,向后继续
 第一循环是给数组A赋值 并且判断条件有关统计某个元素在A数组出现的次数,
 第二个循环是判断条件统计某个元素在P数组出现的次数
 两个数比较即可判断是否进入递归
#include <iostream>
#include <algorithm>
#include<cstdio>
#include<cmath>
using namespace std;
void print_permutation(int n,int *P,int *A,int cur)
{

    if(cur==n)
    {
        for(int i=0; i<n; i++)
            printf("%d",A[i]);
        printf("\n");
    }
    else
        for(int i=0; i<n; i++)
        {
            if (i == 0 || 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,P,A,cur+1);
                }

            }

        }
}
int main()
{
    int n;
    int A[10]={0};
    int P[10];
    cin>>n;
    for (int i=0;i<n;i++)
        scanf("%d",&P[i]);
        sort(P, P+5);
        print_permutation(n,P,A,0);
    return 0;
}



 
 
下一个排列
枚举所有排列的另一个方法是从字典序最小排列开始,不停调用“求下一个排列”的过
程。如何求下一个排列呢?C++的STL中提供了一个库函数next_permutation。看看下面的代
码片段,就会明白如何使用它了。
#include<cstdio>
#include<algorithm> //包含next_permutation
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]); //输出排列p
printf("\n");
} while(next_permutation(p, p+n)); //求下一个排列
return 0;
}
需要注意的是,上述代码同样适用于可重集。
提示7-3:枚举排列的常见方法有两种:一是递归枚举,二是用STL中的
next_permutation。

猜你喜欢

转载自blog.csdn.net/wangzhaoweng/article/details/79180573