next_permutation用法
在标准库算法中,next_permutation应用在数列操作上比较广泛.这个函数可以计算一组数据的全排列.下面是简单的剖析.
首先查看stl中相关信息.
函数原型:
template<class BidirectionalIterator>
bool next_permutation(
BidirectionalIterator _First,
BidirectionalIterator _Last
);
template<class BidirectionalIterator, class BinaryPredicate>
bool next_permutation(
BidirectionalIterator _First,
BidirectionalIterator _Last,
BinaryPredicate _Comp
);
两个重载函数,第二个带谓词参数_Comp,其中只带两个参数的版本,默认谓词函数为"小于".
返回值:bool类型
分析next_permutation函数执行过程:
假设数列 d1,d2,d3,d4……
范围由[first,last)标记,调用next_permutation使数列逐次增大,这个递增过程按照字典序。例如,在字母表中,
abcd的下一单词排列为abdc,但是,有一关键点,如何确定这个下一排列为字典序中的next,而不是next->next->next……
一旦排列到达最大字典序,比如dcba,就返回false,同时重新设置该排列为最小字典序。
返回为true表示仍然存在下一排列。下面着重分析此过程:
根据标记从后往前比较相邻两数据,若前者小于(默认为小于)后者,标志前者为X1(位置PX)表示将被替换,再次从后往前搜索
第一个不小于X的数据,标记为X2。交换X1,X2,然后把[X2,last)标记范围置逆。完成。
要点:为什么这样就可以保证得到的为最小递增。
原数列与新数列不同的数据是X1,并且新数据为X2。[X1,last)总是递减的,[first,X1)没有改变,因为X2>X1,所以不管X2后面怎样排列都比原数列大,
反转[X2,last)使此子数列为最小。从而保证的新数列为原数列的字典序排列next。
另外,库中另一函数prev_permutation与next_permutation相反,由原排列得到字典序中上一次最近排列。
全排列算法有两个比较常见的实现:递归排列和字典序排列。
(1)递归实现
从集合中依次选出每一个元素,作为排列的第一个元素,然后对剩余的元素进行全排列,如此递归处理,从而得到所有元素的全排列。
(2)字典序排列
把升序的排列(当然,也可以实现为降序)作为当前排列开始,然后依次计算当前排列的下一个字典序排列。对当前排列从后向前扫描,
找到一对为升序的相邻元素,记为i和j(i < j)。如果不存在这样一对为升序的相邻元素,则所有排列均已找到,算法结束;否则,
重新对当前排列从后向前扫描,找到第一个大于i的元素k,交换i和k,然后对从j开始到结束的子序列反转,则此时得到的新排列就为下一个字典序排列。
这种方式实现得到的所有排列是按字典序有序的,这也是C++ STL算法next_permutation的思想。
由测试结果可知,自己实现的字典序比next_permutation稍微快点,原因可能是next_permutation版本有额外的函数调用开销;而归实现版本在优化情况下要慢很多,
主要原因可能在于太多的函数调用开销,但在不优化情况下执行比其它二个版本要快,原因可能在于程序结构更简单,执行的语句较少。
比较而言,递归算法结构简单,适用于全部计算出所有的排列(因此排列规模不能太大,计算机资源会成为限制);而字典序排列逐个产生、处理排列,
能够适用于大的排列空间,并且它产生的排列的规律性很强。
字典序法中,对于数字1、2、3......n的排列,不同排列的先后关系是从左到右逐个比较对应的数字的先后来决定的。例如对于5个数字的排列 12354和12345,
排列12345在前,排列12354在后。按照这样的规定,5个数字的所有的排列中最前面的是12345,最后面的是 54321。
字典序算法如下:
设P是1~n的一个全排列:p=p1p2......pn=p1p2......pj-1pjpj+1......pk-1pkpk+1......pn
1)从排列的右端开始,找出第一个比右边数字小的数字的序号j(j从左端开始计算),即 j=max{i|pi<pi+1}
2)在pj的右边的数字中,找出所有比pj大的数中最小的数字pk,即 k=max{i|pi>pj}(右边的数从右至左是递增的,因此k是所有大于pj的数字中序号最大者)
3)对换pi,pk
4)再将pj+1......pk-1pkpk+1pn倒转得到排列p'=p1p2.....pj-1pjpn.....pk+1pkpk-1.....pj+1,这就是排列p的下一个下一个排列。
例如839647521是数字1~9的一个排列。从它生成下一个排列的步骤如下:
自右至左找出排列中第一个比右边数字小的数字4 839647521
在该数字后的数字中找出比4大的数中最小的一个5 839647521
将5与4交换 839657421
将7421倒转 839651247
所以839647521的下一个排列是839651247