白话 STL next_permutation 原理

翻译自stackoverflow 英语好的同学可以自己去看一下。

什么是next permutation

  下面是四个元素{1,2,3,4}的排列

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
...

  每一行都是一个排列。

  我们如何从一个排列转到下一个排列呢?我们可以将如上4个数的排列当做一个数。每一个数的下一个排列就是发现下一个比它大的数。

  next_permutation就是寻找这些元素所组成的数字的升序排列中的下一个数。

   比较绕口,可以看个例子

1 4 2 3 = 1423
1 4 3 2 = 1432 2 1 3 4 = 2134 ....

  1 4 3 2 的next_permutation就是 升序排列中下一个比它大的数2134

如何寻找next_permutation

  还是用1432位例子,现在它是用{1,2,3,4}所能组成的以1开头的最大的四位数。我们想要找到排序中下一个比它大的数该如何呢?

  就是用下一个比1大的元素替代1(就是2),然后加上剩下的三个元素的所能排出的最小数值(也就是134)

  这样就发下了1432的next_permutation 2134

  过程如下:

    1.寻找到1的右边第一个比1大的元素

      1 4 3 2 ----> 2 4 3 1 (注意:这时候的右边三个元素是递减的,我们要求右边三个元素所能排成的最小数值时,只需要将431逆序就可以了)

    2.求出右边元素所能组成的最小排列      

      2 4 3 1 ---->2 1 3 4 <---next_permutation of 1 4 3 2

如何判断一个数应该加入到排列中呢

  那么,我们怎么知道1432是1开头的最大的数呢?观察可以发现,当1的右边也就是432是递减的时候!

  也就是,在一个元素的最右边的元素所排成的序列是递减的时候!

阅读源码

  next_permutation的源码见下面,这里只摘录最重要的部分

while (true) 
{
    It j = i;
    i--;
    if (*i < *j) {
        //something..
    }   
    if (i == begin) {
        //something   
    }
}

  因为i的初值是end,j是i的后一个元素。那么这个while循环可以解释为

while (true)
{
    1如果i的右边是降序,那么做一些事情,返回
    2如果i是开头,那么这就是最后一个排列,做一些事,返回。
    
    继续循环,递减i j 最后总会符合上面条件中的一个
}

  首先解释2,如果i已经是开头了,那么表示这个排列已经完全是递减的(也就是 4,3,2,1)那么我们将整个数组逆序(得到(1,2,3,4),得到排列的初值。

  现在解释1,如果i的右边已经都是降序了,这时候*i < *j (比如i=0,j=1 (1,4,3,2)) 。i代表右边都是降序的最小的下标值。

  这时候,我们需要做的就是从右边,找到第一个大于i的值,然后将他和i交换,然后再将右边的整体逆序。

  代码如下:

It k = end;

while (!(*i < *--k))
    /* pass */;

iter_swap(i, k);
reverse(j, end);
return true;

 

附:next_permutation代码

#include <vector>
#include <iostream>
#include <algorithm>

using namespace std;

template<typename It>
bool next_permutation(It begin, It end)
{
        if (begin == end)
                return false;

        It i = begin;
        ++i;
        if (i == end)
                return false;

        i = end;
        --i;

        while (true)
        {
                It j = i;
                --i;

                if (*i < *j)
                {
                        It k = end;

                        while (!(*i < *--k))
                                /* pass */;

                        iter_swap(i, k);
                        reverse(j, end);
                        return true;
                }

                if (i == begin)
                {
                        reverse(begin, end);
                        return false;
                }
        }
}

int main()
{
        vector<int> v = { 1, 2, 3, 4 };

        do
        {
                for (int i = 0; i < 4; i++)
                {
                        cout << v[i] << " ";
                }
                cout << endl;
        }
        while (::next_permutation(v.begin(), v.end()));
}

 

posted on 2014-08-21 16:16  kevink  阅读(503)  评论(0编辑  收藏  举报

导航