在刷题的时候,next_permutation是非常常用的一个函数,像leetcode的Next Permutation,Permutation Sequence,Permutations,Permutations II都可以用这个函数来偷懒。
函数原型:
template<class BidirectionalIterator>
bool next_permutation(
BidirectionalIterator _First,
BidirectionalIterator _Last
);
template<class BidirectionalIterator, class BinaryPredicate>
bool next_permutation(
BidirectionalIterator _First,
BidirectionalIterator _Last,
BinaryPredicate _Comp
);
函数实现原理:
在当前序列中,从尾端往前寻找两个相邻元素,前一个记为*i,后一个记为*ii,并且满足*i < *ii。然后再从尾端寻找另一个元素*j,如果满足*i < *j,即将第i个元素与第j个元素对调,并将第ii个元素之后(包括ii)的所有元素颠倒排序,即求出下一个序列了。
实现代码:
template<class BidirectionalIterator>
bool next_permutation(
BidirectionalIterator first,
BidirectionalIterator last
)
{
if(first == last)
return false; //空序列没有next_permutation
BidirectionalIterator i = first;
++i;
if(i == last)
return false; //只有一个元素,没有next_permutation
i = last;
--i; //指向最后一个元素
for(;;) {
BidirectionalIterator ii = i; //第一个循环ii指向最后一个元素, 第二个循环指向倒数第二个
--i; //第二个循环i指向倒数第二个元素, 第二个循环指向倒数第三个元素...
if(*i < *ii) { //找到满足*i < *ii的地方
BidirectionalIterator j = last; //从尾端寻找另一个元素*j
while(!(*i < *--j)); //如果满足*i < *j
iter_swap(i, j); //即将第i个元素与第j个元素对调
reverse(ii, last); //并将第ii个元素之后(包括ii)的所有元素颠倒排序
return true; //即求出下一个序列了
}
if(i == first) { //如果找不到满足*i < *ii的地方,说明这是最大的字典序
reverse(first, last); //全逆向,即为最小字典序列,如cba变为abc
return false;
}
}
}
从上面的代码注释来看,完全是按照函数实现原理的描述来的。在STL中,除了next_permutation外,还有一个函数prev_permutation,两者都是用来计算排列组合的函数。前者是求出下一个排列组合,而后者是求出上一个排列组合。 prev_permutation实现类似,就是反向查找。
另外需要注意的是,虽然最后一个排列没有下一个排列,用next_permutation会返回false,但是使用了这个方法后,序列会变成字典序列的第一个,如cba变成abc。prev_permutation同理。
使用next_permutation:
下面附上codeEval中的一道题STRING PERMUTATIONS的代码,我就是利用next_permutation做的:
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <vector>
#include <algorithm>
using namespace std;
int main (int argc, char* argv[]) {
ifstream file;
file.open(argv[1]);
string lineBuffer;
while (!file.eof()) {
vector<string> v;
getline(file, lineBuffer);
sort(lineBuffer.begin(), lineBuffer.end());
cout << lineBuffer;
while (next_permutation(lineBuffer.begin(), lineBuffer.end())) {
cout << "," << lineBuffer;
}
cout << endl;
v.clear();
}
return 0;
}