• 打印全排列和stl::next_permutation


    打印全排列是个有点挑战的编程问题。STL提供了stl::next_permutation完美的攻克了这个问题。

    可是,假设不看stl::next_permutation,尝试自己解决,怎么做?

    非常自然地,使用递归的办法:

    1. 单个元素的排列仅仅有1个。

    2. 多个元素的排列能够转化为:

        以每一个元素为排列的首个元素,加上其它元素的排列。


    有了思路,就能够编码了。

    第一个版本号:

    void printAllPermutations(const std::string& prefix, int set[], int n)
    {
             using namespace std;
             char buffer[12];
             for (int i=0; i<n; ++i)
            {
                    string tmp_prefix(prefix);
                     int ei = set[i];
                    _itoa_s(ei, buffer, 12, 10);
                    tmp_prefix += buffer;
                     if (n == 1)
                    {
                            cout << tmp_prefix.c_str() << endl;
                    }
                     else
                    {
                            tmp_prefix += " " ;

                             // shift set[0,i) to right by 1
                             for (int j=i-1; j>=0; --j)
                            {
                                    set[j+1] = set[j];
                            }

                            printAllPermutations(tmp_prefix, set+1, n-1);

                             // shift set[0,i) to left by 1
                             for (int j=0; j<i; ++j)
                            {
                                    set[j] = set[j+1];
                            }
                            set[i] = ei;
                    }
            }
    }


    測试:

    int myints[] = {1,2,3,4};
    printAllPermutations( "" , myints, 4);

    通过。


    这样的方法的缺点是产生了大量的string对象。

    怎么避免呢?

    第二个版本号:

    void printAllPermutations2(int set[], int n, int from)
    {
             using namespace std;
             for (int i=from; i<n; ++i)
            {
                     int ei = set[i];
                     if (from == n-1)
                    {
                             // it is possible use callback here instead of printing a permutation
                             for (int j=0; j<n; ++j)
                            {
                                    cout << set[j] << ' ' ;
                            }
                            cout << endl;
                    }
                     else
                    {
                             // shift set[from,i) to right by 1
                             for (int j=i-1; j>=from; --j)
                            {
                                    set[j+1] = set[j];
                            }
                            set[from] = ei;

                            printAllPermutations2(set, n, from+1);

                             // shift set[from,i) to left by 1
                             for (int j=from; j<i; ++j)
                            {
                                    set[j] = set[j+1];
                            }
                            set[i] = ei;
                    }
            }
    }


    測试:

    int myints[] = {1,2,3,4};
    printAllPermutations2(myints, 4, 0);

    通过。


    第二个版本号相比第一个版本号的还有一个改进是能够非常easy地改变成回调函数的形式,扩展函数的用途。而不不过打印排列。


    似乎非常不错了。

    可是和stl::next_permutation相比,以上的方案就太逊了。

    1. stl::next_permutation支持部分排列,而不必是全排列。你能够从不论什么一个排列開始,能够随时退出next_permutation循环。

    2. stl::next_permutation支持多重集的排列。比如:

    int myints[] = {1,2,2,2};
    do {
       std::cout << myints[0] << ' ' << myints[1] << ' ' << myints[2] << ' ' << myints[3] << ' ';
    } while ( std::next_permutation(myints,myints+4) );
    输出:

    1 2 2 2
    2 1 2 2
    2 2 1 2
    2 2 2 1

    没有反复的排列。


    stl::next_permutation这么强大,非常值得看看它到底是怎么实现的。

    // TEMPLATE FUNCTION next_permutation
    template < class _BidIt> inline
             bool _Next_permutation(_BidIt _First, _BidIt _Last)
            {        // permute and test for pure ascending, using operator<
            _DEBUG_RANGE(_First, _Last);
            _BidIt _Next = _Last;
             if (_First == _Last || _First == --_Next)
                     return (false );

             for (; ; )
                    {        // find rightmost element smaller than successor
                    _BidIt _Next1 = _Next;
                     if (_DEBUG_LT(*--_Next, *_Next1))
                            {        // swap with rightmost element that's smaller, flip suffix
                            _BidIt _Mid = _Last;
                             for (; !_DEBUG_LT(*_Next, *--_Mid); )
                                    ;
                            std::iter_swap(_Next, _Mid);
                            std::reverse(_Next1, _Last);
                             return (true );
                            }

                     if (_Next == _First)
                            {        // pure descending, flip all
                            std::reverse(_First, _Last);
                             return (false );
                            }
                    }
            }

    template < class _BidIt> inline
             bool next_permutation(_BidIt _First, _BidIt _Last)
            {        // permute and test for pure ascending, using operator<
             return _Next_permutation(_CHECKED_BASE(_First), _CHECKED_BASE(_Last));
            }

    代码不长,但须要研究才干理解。

    非常多算法都是这种。

    这个算法能够概括为:

    假设仅仅有零个或一个元素,返回false,表示回到全排列的起点。

    否则。从右边開始。找到第一个不是递减的元素,即E(i) < E(i+1),从E(i+1)一直到E(n)都是不增的。

            假设找到。从右边開始。找到大于E(i)的那个元素E(x)【一定会找到】,交换E(i)和E(x),然后把E[i+1, n]范围内的元素反转。

    返回true。


           假设找不到,把整个范围内的元素反转,返回false,表示回到全排列的起点。

    为什么这个算法可行呢?看以下1 2 3 4的全排列。

    能够非常easy地看到,

    假设把每一个排列看成一个数,那么下一个排列大于上一个排列。

    由上可知,第一个排列是最小排列【不减排列】。最后一个排列是最大排列【不增排列】。

    最小排列和最大排列是反序的关系。

    算法的关键:从E(i+1)一直到E(n)都是不增的。

    这个特性说明,这一范围的元素的排列是一个最大排列,下一个排列必然是找到这一范围内大于这一范围的前一元素的元素,交换这两个元素,交换后E[i+1, n]仍为不增排列【最大排列】。反转之后,变成最小排列。这样处理后得到的排列正好是E[0,n]的下一个排列。

    1 2 3 4

    1 2 4 3
    1 3 2 4
    1 3 4 2
    1 4 2 3
    1 4 3 2
    2 1 3 4
    2 1 4 3
    2 3 1 4
    2 3 4 1
    2 4 1 3
    2 4 3 1
    3 1 2 4
    3 1 4 2
    3 2 1 4
    3 2 4 1
    3 4 1 2
    3 4 2 1
    4 1 2 3
    4 1 3 2
    4 2 1 3
    4 2 3 1
    4 3 1 2
    4 3 2 1



  • 相关阅读:
    Linux进程相关的一些笔记
    [Project Euler] 来做欧拉项目练习题吧: 题目007
    [Project Euler] 来做欧拉项目练习题吧: 题目015
    [Project Euler] 来做欧拉项目练习题吧: 题目009
    [Project Euler] 来做欧拉项目练习题吧: 题目017
    [Project Euler] 来做欧拉项目练习题吧: 题目014
    [Project Euler] 来做欧拉项目练习题吧: 题目013
    [Project Euler] 来做欧拉项目练习题吧: 题目006
    [Project Euler] 来做欧拉项目练习题吧: 题目008
    [Project Euler] 来做欧拉项目练习题吧: 题目012
  • 原文地址:https://www.cnblogs.com/liguangsunls/p/7148987.html
Copyright © 2020-2023  润新知