【经典算法】STL之next_permutation be52 和prev_permutation
2015-09-08 21:09
330 查看
next_permutation和prev_permutation这两个算法的实现,非常的经典,同样十分的有趣。
第一次听说大约是在大四的时候,听一个准备面试的学长说起。
今天又遇到同事正好问了面试的同学这个问题,遂记录下来。
首先,来弄明白什么是"下一个"排列,或者"上一个"排列。
举个例子,abc组成的一个排列,以字典序来说,abc,acb,bac,bca,cab,cba,一共是六种。
这样abc最小,cba最大。比如说到bac,下一个排列就是bca,上一个排列acb。
先来看看next_permutation
(1)如果采用直接枚举所有的排列,然后找出下一个排列,显得有点笨拙。
但凡说起算法,自然会有巧妙的解法。为此,又仔细去翻看侯捷先生的经典著作《STL源码剖析》。
基本思路:从最末尾向前面找两个相邻的元素i和j,并且满足i<j。然后从最末尾继续向前找第一个大于i的元素k,
将i和k交换,并将j之后的元素颠倒得到的序列即为所求。
描述有点抽象,还是来点形象的。
1743256------->1743265
这里i=5,j=6,第一个比大于i的是k=6。
175432------->275431----->271345
这里i=1,j=7,第一个比大于i的元素k=2。
为什么是这样的?
我们来看下面一个事实
令F为i之前的数,E为j之后小于j的数,E必定为一个从大到小的全排列(形如54321这种,如若不然,必定存在一组数,使得左边小于右边,如53421),
则原来的数FijE。
存在这样的事实,ijE组成的序列中一定存在新的排列比ijE大,因为至少jiE这个数是满足的。
这时从上面FijE最末端寻找一个数k,使得它第一个大于i,这个数可以是j。
1>这里若E中的数均小于i,则k为j,那么显然交换i和E中的某个数,这个新组成的数值小于ijE。
那么只能交换i和j。这时新组成的数jiE,一定大于ijE。因为ji>ij,所以新组成的数jiE,固定前两位ji,找到E中数的最小排列,则是直接将E颠倒即可。
这是因为E是一个从大到小的全排列。
2>这里若E中存在某个数大于i,则将这个数记为k,于是将E记为SkT,S为k之前的数的排列(大于i),T为k之后的数的排列(不大于i)。
k是从最末尾比较,第一个比i大的数。与i交换的数中,k肯定是E中满足大于i最小的那一个。于是,交换之后kj>ij,然后固定kj,求SiT构成的序列的最小排列。
由前述(1)中同理,颠掉SiT,即可。
好了,到这里基本就知道next_permutation的计算方式了。
(2)prev_permutation
知道了next_permutation怎么求,prev_permutation就比较简单了。
基本思路:
从最末尾向前面找两个相邻的元素i和j,并且满足i>j。然后从最末尾继续向前找第一个小于i的元素k,
将i和k交换,并将j之后的元素颠倒得到的序列即为所求。
第一次听说大约是在大四的时候,听一个准备面试的学长说起。
今天又遇到同事正好问了面试的同学这个问题,遂记录下来。
首先,来弄明白什么是"下一个"排列,或者"上一个"排列。
举个例子,abc组成的一个排列,以字典序来说,abc,acb,bac,bca,cab,cba,一共是六种。
这样abc最小,cba最大。比如说到bac,下一个排列就是bca,上一个排列acb。
先来看看next_permutation
(1)如果采用直接枚举所有的排列,然后找出下一个排列,显得有点笨拙。
但凡说起算法,自然会有巧妙的解法。为此,又仔细去翻看侯捷先生的经典著作《STL源码剖析》。
基本思路:从最末尾向前面找两个相邻的元素i和j,并且满足i<j。然后从最末尾继续向前找第一个大于i的元素k,
将i和k交换,并将j之后的元素颠倒得到的序列即为所求。
描述有点抽象,还是来点形象的。
1743256------->1743265
这里i=5,j=6,第一个比大于i的是k=6。
175432------->275431----->271345
这里i=1,j=7,第一个比大于i的元素k=2。
为什么是这样的?
我们来看下面一个事实
令F为i之前的数,E为j之后小于j的数,E必定为一个从大到小的全排列(形如54321这种,如若不然,必定存在一组数,使得左边小于右边,如53421),
则原来的数FijE。
存在这样的事实,ijE组成的序列中一定存在新的排列比ijE大,因为至少jiE这个数是满足的。
这时从上面FijE最末端寻找一个数k,使得它第一个大于i,这个数可以是j。
1>这里若E中的数均小于i,则k为j,那么显然交换i和E中的某个数,这个新组成的数值小于ijE。
那么只能交换i和j。这时新组成的数jiE,一定大于ijE。因为ji>ij,所以新组成的数jiE,固定前两位ji,找到E中数的最小排列,则是直接将E颠倒即可。
这是因为E是一个从大到小的全排列。
2>这里若E中存在某个数大于i,则将这个数记为k,于是将E记为SkT,S为k之前的数的排列(大于i),T为k之后的数的排列(不大于i)。
k是从最末尾比较,第一个比i大的数。与i交换的数中,k肯定是E中满足大于i最小的那一个。于是,交换之后kj>ij,然后固定kj,求SiT构成的序列的最小排列。
由前述(1)中同理,颠掉SiT,即可。
好了,到这里基本就知道next_permutation的计算方式了。
(2)prev_permutation
知道了next_permutation怎么求,prev_permutation就比较简单了。
基本思路:
从最末尾向前面找两个相邻的元素i和j,并且满足i>j。然后从最末尾继续向前找第一个小于i的元素k,
将i和k交换,并将j之后的元素颠倒得到的序列即为所求。
相关文章推荐
- 动易2006序列号破解算法公布
- Ruby实现的矩阵连乘算法
- C#插入法排序算法实例分析
- 超大数据量存储常用数据库分表分库算法总结
- C#数据结构与算法揭秘二
- C#冒泡法排序算法实例分析
- 算法练习之从String.indexOf的模拟实现开始
- C#算法之关于大牛生小牛的问题
- C#实现的算24点游戏算法实例分析
- c语言实现的带通配符匹配算法
- 浅析STL中的常用算法
- STL区间成员函数及区间算法总结
- 算法之排列算法与组合算法详解
- C++实现一维向量旋转算法
- Ruby实现的合并排序算法
- C#折半插入排序算法实现方法
- 基于C++实现的各种内部排序算法汇总
- C++线性时间的排序算法分析
- C++实现汉诺塔算法经典实例