zoukankan      html  css  js  c++  java
  • <转载>全排列算法注记

    全排列算法有两个比较常见的实现:递归排列和字典序排列。

    (1)递归实现
    从集合中依次选出每一个元素,作为排列的第一个元素,然后对剩余的元素进行全排列,如此递归处理,从而得到所有元素的全排列。算法实现如下:

    #include <iostream>
    #include <algorithm>
    using namespace std;

    template <typename T>
    void CalcAllPermutation_R(T perm[], int first, int num)
    {
        if (num <= 1) {
            // 得到一个排列
    //        int count = first + num;
    //        for (int i = 0; i < count; ++i)
    //            cout << perm[i] << ' ';
    //        cout << '\n';
            return;
        }
       
        for (int i = first; i < first + num; ++i) {
            swap(perm[i], perm[first]);
            CalcAllPermutation_R(perm, first + 1, num - 1);
            swap(perm[i], perm[first]);
        }
    }

    int main()
    {
        const int NUM = 12;
        char perm[NUM];
       
        for (int i = 0; i < NUM; ++i)
            perm[i] = 'a' + i;
       
        CalcAllPermutation_R(perm, 0, NUM);
    }

    程序运行结果(优化):
    -bash-3.2$ g++ test.cpp -O3 -o ttt
    -bash-3.2$ time ./ttt

    real    0m10.556s
    user    0m10.551s
    sys     0m0.000s

    程序运行结果(不优化):
    -bash-3.2$ g++ test.cpp -o ttt
    -bash-3.2$ time ./ttt

    real    0m21.355s
    user    0m21.332s
    sys     0m0.004s


    (2)字典序排列
    把升序的排列(当然,也可以实现为降序)作为当前排列开始,然后依次计算当前排列的下一个字典序排列。对当前排列从后向前扫描,找到一对为升序的相邻元素,记为i和j(i < j)。如果不存在这样一对为升序的相邻元素,则所有排列均已找到,算法结束;否则,重新对当前排列从后向前扫描,找到第一个大于i的元素k,交换i和k,然后对从j开始到结束的子序列反转,则此时得到的新排列就为下一个字典序排列。这种方式实现得到的所有排列是按字典序有序的,这也是C++ STL算法next_permutation的思想。算法实现如下:

    #include <iostream>
    #include <algorithm>
    using namespace std;

    template <typename T>
    void CalcAllPermutation(T perm[], int num)
    {
        if (num < 1)
            return;
       
        // 输出第一个排列
    //    for (int i = 0; i < num; ++i)
    //        cout << perm[i] << ' ';
    //    cout << '\n';
       
        while (true) {
            int i;
            for (i = num - 2; i >= 0; --i) {
                if (perm[i] < perm[i + 1])
                    break;
            }
           
            if (i < 0)
                break;  // 已经找到所有排列
       
            int k;
            for (k = num - 1; k > i; --k) {
                if (perm[k] > perm[i])
                    break;
            }
           
            swap(perm[i], perm[k]);
            reverse(perm + i + 1, perm + num);
           
            // 得到一个新的排列
    //        for (int i = 0; i < num; ++i)
    //            cout << perm[i] << ' ';
    //        cout << '\n';
        }
    }

    int main()
    {
        const int NUM = 12;
        char perm[NUM];
       
        for (int i = 0; i < NUM; ++i)
            perm[i] = 'a' + i;
       
        CalcAllPermutation(perm, NUM);
    }

    程序运行结果(优化):
    -bash-3.2$ g++ test.cpp -O3 -o ttt
    -bash-3.2$ time ./ttt

    real    0m3.055s
    user    0m3.044s
    sys     0m0.001s

    程序运行结果(不优化):
    -bash-3.2$ g++ test.cpp -o ttt
    -bash-3.2$ time ./ttt

    real    0m24.367s
    user    0m24.321s
    sys     0m0.006s


    使用std::next_permutation来进行对比测试,代码如下:
    #include <iostream>
    #include <algorithm>
    using namespace std;

    template <typename T>
    size_t CalcAllPermutation(T perm[], int num)
    {
        if (num < 1)
            return 0;
       
        // 输出第一个排列
    //    for (int i = 0; i < num; ++i)
    //        cout << perm[i] << ' ';
    //    cout << '\n';
       
        size_t count = 0;
        while (next_permutation(perm, perm + num)) {
            ++count;
           
            // 得到一个新的排列
    //        for (int i = 0; i < num; ++i)
    //            cout << perm[i] << ' ';
    //        cout << '\n';
        }
       
        return count;
    }

    int main()
    {
        const int NUM = 12;
        char perm[NUM];
       
        for (int i = 0; i < NUM; ++i)
            perm[i] = 'a' + i;
       
        size_t count = CalcAllPermutation(perm, NUM);
       
        return count;
    }

    程序运行结果(优化):
    -bash-3.2$ g++ test.cpp -O3 -o ttt
    -bash-3.2$ time ./ttt

    real    0m3.606s
    user    0m3.601s
    sys     0m0.002s

    程序运行结果(不优化):
    -bash-3.2$ g++ test.cpp -o ttt
    -bash-3.2$ time ./ttt

    real    0m33.850s
    user    0m33.821s
    sys     0m0.006s

    测试结果汇总一(优化):
    (1)递归实现:0m10.556s
    (2-1)字典序实现:0m3.055s
    (2-2)字典序next_permutation:0m3.606s

    测试结果汇总二(不优化):
    (1)递归实现:0m21.355s
    (2-1)字典序实现:0m24.367s
    (2-2)字典序next_permutation:0m33.850s

    由测试结果可知,自己实现的字典序比next_permutation稍微快点,原因可能是next_permutation版本有额外的函数调用开销;而归实现版本在优化情况下要慢很多,主要原因可能在于太多的函数调用开销,但在不优化情况下执行比其它二个版本要快,原因可能在于程序结构更简单,执行的语句较少。

    比较而言,递归算法结构简单,适用于全部计算出所有的排列(因此排列规模不能太大,计算机资源会成为限制);而字典序排列逐个产生、处理排列,能够适用于大的排列空间,并且它产生的排列的规律性很强。

  • 相关阅读:
    洛谷 P1508 Likecloud-吃、吃、吃
    Codevs 1158 尼克的任务
    2017.10.6 国庆清北 D6T2 同余方程组
    2017.10.6 国庆清北 D6T1 排序
    2017.10.3 国庆清北 D3T3 解迷游戏
    2017.10.3 国庆清北 D3T2 公交车
    2017.10.3 国庆清北 D3T1 括号序列
    2017.10.4 国庆清北 D4T1 财富
    2017.10.7 国庆清北 D7T2 第k大区间
    2017.10.7 国庆清北 D7T1 计数
  • 原文地址:https://www.cnblogs.com/xiangshancuizhu/p/1913158.html
Copyright © 2011-2022 走看看