参考于:【STL】next_permutation的原理和使用
给定一个数列,如何得到它的全排列?
例如1 2 3,它的全排列是123,132,213,231,312,321。
全排列的关键在于,给定某一数列,能从该数列推出“下一个”数列。
那么如何找“下一个”数列呢?
找“下一个”数列的算法思路描述如下:
1、从后往前找两个相邻元素,前一个位置记为i,后一个记为j,并且满足s[i] < s[j]
2、从后往前找另一个位置k,若满足s[i] < s[k],则将s[i]与s[k]交换,并将j之后(包括j)的所有元素颠倒排序,即得“下一个”数列。
先试着自己按照这个算法思路去模拟一下算法过程,看能不能悟出这个算法为什么是对的?如果还是不明白这个算法为什么是对的,再继续往下看。
举个例子,6 2 5 4 3 1,观察一下,下一个数列是6 3 1 2 4 5,回忆一下观察的过程,你是先从后往前找的对不对?你在找什么?
其实你在从后往前找,比较相邻两个数字,如果前一个数字比后一个数字大或相等,就继续往前找,直到找到前一个数字比后一个数字小,我们将前一个数字的位置记为i,后一个数字的位置记为j,此时应该满足s[i]<s[j]。这个例子中i = 1,j = 2。
可以看出从j开始的数列都是非升序排列的,因为刚刚在找的过程中,前一个数字都是比下一个数字大或相等。
要知道,非升序排列的数列不可能有“下一个”数列,即非升序排列的数列是“最大的”。那么从j开始的数列必定不存在“下一个”数列;另外一点,我们需要考虑i之前(不包括i)的数列吗?比如例子中的6,不需要!因为从i开始的数列必定存在“下一个”数列,因为它不是非升序排列的,非升序排列的数列必定存在“下一个”数列。所以结论是,我们只考虑从从i开始的数列找“下一个”数列。
从j开始的数列不可能有“下一个”数列,那么“下一个”数列的i位置放的值不能是原数,而应该从后面的数找,找比原数大的,然后这个数要尽量小。那么如何找到这个最小的数呢?因为从j开始的数列是非升序,所以从后往前找,找到比原数大的就行了。这个位置,我们记为k,例子中这个最小的数是3,即k=4。
找到k之后,交换i,k位置的值(一定可以找到这样的k,因为j位置就满足了)。交换之后,从j开始的数列依然是非升序排列。这个很明显,就不多解释了。
因为从j开始的数列是非升序排列,逆序一下,得到非降序排列,非降序排列是“最小的”。所以交换之后,从j开始,逆序排序一下,即得到“下一个”排列。
下面自己实现的源代码(原理同STL中的next_permutation)
bool nextPermutation(int s[], int first, int last) //last最后一个元素的下一个位置 { if (last-first <= 1) //保证至少2个元素 return false; int i = last-2, j = last-1; while (1) { if (s[i] < s[j]) //只要存在这样的对,下一个排列就必定存在 { int k = last; while (s[i] >= s[--k]); swap(s[i], s[k]); reverse(s+j, s+last); return true; } if (i == first) //找到第一个元素都找不到,说明该数组是降序排列的,下一个排列不存在 { reverse(s+first, s+last); return false; } i--, j--; } }
ps.理解源代码之后就可以知道为什么降序排列的数组,用STL中的next_permutation之后变成升序排列
最后再补充一个不规范的递归版本,所谓不规范,就是排列的顺序与STL中的next_permutation不一样。
void nextPermutationRecursive(int s[], int n, int first, int last) //last是最后一个元素的下一个位置 { if (last-first == 1) //只含一个元素 { for (int i = 0; i < n; ++i) cout << s[i]; cout << endl; } else { for (int i = first; i < last; ++i) { swap(s[first], s[i]); nextPermutationRecursive(s, n, first+1, last); swap(s[first], s[i]); } } }