打印全排列和stl::next_permutation

10 篇文章 0 订阅

打印全排列是个有点挑战的编程问题。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);

通过。


第二个版本相比第一个版本的另一个改进是可以很容易地改变成回调函数的形式,扩展函数的用途,而不仅仅是打印排列。


似乎很不错了。

但是和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] <<  '\n' ;
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的全排列。

可以很容易地看到,

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

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

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

算法的关键:从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



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值