暴力求解法中的枚举排列,生成全排列

  **对于一个长度为n数组长度的数组={0,1,2,3,..., n-1}。要想枚举它的所有的长度为n的全排列出来。**
  有两种选择:一个是直接枚举, 另外一个是使用递归来构造。
  先说最容易理解的直接枚举。 例如当 n=5 的时候,我们生成的排列从小到大有 1234,1243,1324,..... ,4321。很容易地,我们可以用4个for循环搞定。
  代码如下:
#include
#include
#include
#include
using namespace std;
int main()
{
    int n = 5;
    for(int a = 0; a < n; a++)
    {
        for(int b = 0; b < n; b++)
        {
            if(b == a) continue;
            for(int c = 0; c < n; c++)
            {
                if(c == a || c == b) continue;
                for(int d = 0; d < n; d++)
                {
                    if(d == a || d == b || d == c) 
                        continue;
                        cout <return 0;
}
    在这个算法里面我们用*continue*来直接跳过输出重复的排列,避免运算不必要的解,从而降低时间复杂度。但是这个算法有遇到 n 更大的时候就要写 n-1个for循环,看过《啊哈算法》的都知道 里面有一个9个for循环的求解。。。。。。这个就不用多说了。
  继续讨论n更加大的时候的问题, 前面说了,当n等于10的时候我们不至于真的去写9个for循环吧, 这时候我们可以用递归构造的方法来枚举。
  递归函数: print_permutation(int n, int *A, int cur)
  递归使用需要有判定界限,定义一个数组A,往A[]里面放我们需要排列的元素P[], 我们的判定界限则是: 当 n=cur 时 return 。
  代码如下:
#include
#include
#include
#include
using namespace std;
void print_permutation(int n, int* A, int cur)
{
    if(n == cur)//排列到n个长度后输出
    {
        for(int i = 0; i < n; i++)
            cout << A[i];
        cout << endl;
        return;
    }
    else
    {
        for(int i = 0; i < n; i++)
        {
            int ok = 1;
            for(int j = 0; j < cur; j++)
            //判断i是不是已经使用过了
                if(A[j] == i)
                    ok = 0;
            if(ok)
            {
                A[cur] = i;
                print_permutation(n, A, cur + 1);
            }
        }
    }
}
int main()
{
    int P[100], A[100] = {0};
    int n;
    cin >> n;
    cout << "0 - n-1 的全排列" << endl;
    print_permutation(n, A, 0);
    return 0;
}

说明一下,在上面上我们默认了P[] = {0, 1, 2, … , n-1}是没有重复的元素的集合,所以就直接不用传入P[]数组了。
#
现在我们更进一步地讨论, 如果P[4]= {1, 1,2,2}呢,这个是有多个重复的元素的集合。要是我们继续使用递归构造的话,上面的放那个法就需要改进一下了。
我们需要考虑的问题如下:


  1. 当P数组中存在重复数组的时候,我们怎么判断函数已经历遍了所有的元素,例如要生成排列 1122,A[0] = 1时,怎么使A[1] = 1,A[2]=2, A[3]=2 ?

  2. 当P中存在重负元素是我们又怎么避开多次使用相同元素作为跟节点展开?即 已经生成了排列以A[0] = 1为开头的排列 1122, A[0] = 1为开头的排列 1122 是不再允许被生成的。


对于问题1,我们可以在本次操作把P[i]插入A[cur]时候统计好P[i]在数组A的 0 - cur-1 元素中 P[i]出现的次数 c2,和P[i] 在 数组P中出现的个数c1。只要c1 > c2, 则P[i] 可以被继续插入到 A中。
对于问题2, 我们事先对P数组从小到大排序,对于本次需要最为根节点展开的元素 P[i] ,我们判断 P[i] 是否与 P[i] 的上一个元素相等, 如果 P[i]=P[i-1] 的话则说明,对于P[i]相同的值在上一个同等高度的节点上已经使用过了,需要剪枝来避免重复打印。
生成可重复元素排列的代码如下:

//含有可重复的元素数组的全排列

#include
#include
#include
#include
using namespace std;
void print_permutation(int n, int *P, int *A, int cur)
{
    if(n == cur)//排列到n个长度后输出
    {
        for(int q = 0; q < n; q++)
            cout << A[q];
        cout << endl;
        return;
    }
    else
    {
        for(int i = 0; i < n; i++)
        {
            if(0 == i || P[i] != P[i-1])
            /*判断是否当前使用的素是不是已经作为
            **在同一个集合里拓展过的**/
            {
                int c1 = 0, c2 = 0;
                //判断重复的元素是不是已经完全使用了
                for(int j = 0; j < n; j++)
                    if(P[i] == P[j])
                        c1++;
                for(int j = 0; j < cur; j++)
                    if(P[i] == A[j])
                        c2++;
                if(c1 > c2)
                {
                    A[cur] = P[i];
                    print_permutation(n,P,A,cur+1);
                    //递归
                }
            }
        }
    }
}
int main()
{
    int P[100], A[100] = {0};
    int n;
    cin >> n;
    for(int i = 0; i < n; i++)
        cin >> P[i];
    sort(P, P+n);
    cout << "可重复元素排列" << endl;
    print_permutation(5, P, A, 0);
    return 0;
}

生成全排列的方法也可以用STL里的 next_permutation() 来实现用法如下:


#include
#include
#include
#include
using namespace std;
int main()
{
    int P[100], A[100] = {0};
    int n;
    cin >> n;
    for(int i = 0; i < n; i++)
        cin >> P[i];
    sort(P, P+n);
    do{
        for(int i = 0; i < n; i++)
            cout << P[i];
        cout << endl;
    }while(next_permutation(P, p+n));

    return 0;
}

以上内容是个人基于对《算法竞赛入门经典第二版》暴力求解法部分章节的理解写出来的,觉得大神们觉得有不当之处请多多指教。

你可能感兴趣的:(暴力,递归,枚举)