白话 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())); }