STL next_permutation 算法原理和自行实现

目标

STL中的next_permutation 函数和 prev_permutation 两个函数提供了对于一个特定排列P,求出其后一个排列P+1和前一个排列P-1的功能。

这里我们以next_permutation 为例分析STL中实现的原理,prev_permutation 的原理与之类似,我们在最后给出它们实现上差异的比较

问题:

给定一个排列P,求出其后一个排列P+1是什么。

思路

按照字典序的定义不难推出,正序,是一组排列中最小的排列,而逆序,则是一组排列中最大的排列。

从字典序的定义出发,我们可以看到,n个元素的排列全排列中,从给定排列P 求解下一排列P+1 ,假设两个排列有前k位是相同的(0<=k<=n),那么我们只需要在后面n-k个元素的排列 P(n-k)中求得下一个排列即可

既然我们关心的是后面 n-k位的排列,那么不妨开开脑洞,从后向前考察排列。

首先我们举一个比较极端的例子:排列 1 2 3 4 5

很显然,这是一个正序排列(递增序列),因此这是这几个数字所组成的排列中最小的排列,记为P1.

现在我们要求出P2,P2是 1 2 3 5 4. 我们可以看到,P2的前三位和P1的前三位的排列完全相同,唯一的变化是最后两位颠倒顺序,这一顺序的颠倒有何玄机呢?——使得最后两位从正序的 4 5 变成了逆序的 5 4.

接着求P3.P3是 1 2 4 3 5. 我们看到,最后两位已经是逆序,不可能有字典序更大的排列,因此必须考虑更多的位,在后3个元素中,3 5 4 显然不是逆序,因此一定存在字典序更大的排列方式,我们由此确定了n-k==3

我们现在要在 3 5 4 中求得下一个排列,3 5 4 不是一个逆序,是因为 3 后面有元素大于3 。我们要在大于3的数字中选择最小的那个,和3交换,因为这样可以保证得到最小的首位元素。对于这个例子,我们选择将3和4进行交换,而不是3 和 5,这样得到的首位元素是4. 现在我们得到了排列 4 5 3 。

显然,4 5 3 并不是我们想要的下一个排列,下一个排列是 4 3 5. 观察区别,不难看出,首位元素一定是4,但是5 3 这个子排列是一个逆序排列

为什么会是逆序排列?

因为我们寻找的时候就以是不是逆序为分割点,3 恰好是第一个非逆序的元素,而4作为与3 交换的元素,又比3要大,因此交换后得到的 5 3 一定是逆序的排列。

逆序排列没有下一排列,但是将逆序排列反向后,我们就得到了对应的正序排列,而正序排列是当前元素所能形成的最小排列,因此,4 3 5 是4 为首位元素所能形成最小排列,而前3 位没有变化,故我们得到了下一排列P3.

另外,大于3的最小元素,即4 ,也是第一个大于3的元素,因为 5 4 是个逆序排列。

更一般地,例如对于可重集排列 1 2 3 7 6 5 2 1

我们首先寻找第一个非逆序元素,这里是3,然后从后向前寻找第一个大于3的元素,这里是5,交换,得到 5 7 6 3 2 1 的子排列,然后反向,即可得到下一排列。如果没有找到第一个非逆序元素,那么说明该排列已经是最大排列。

代码实现:

以字符串为例,实现next_permutation,这里的空的for语句主要是为了压行

 1 /*
 2 *算法实现:STL中的next_permutation实现
 3 */
 4 #include<cstdio>
 5 #include<cstring>
 6
 7 void inline swap(char *s1,char *s2){
 8     char t=*s1;
 9     *s1=*s2;
10     *s2=t;
11 }
12 /**
13 *反转字符串函数,s,e分别执行字符串的开始和结尾,不能反转中文
14 **/
15 void reverse(char *s,char* e){
16     for(e--;s<e;s++,e--)swap(s,e);
17 }
18
19 bool next_permutation(char *start,char *end){
20     char *cur = end-1, *pre=cur-1;
21     while(cur>start && *pre>=*cur)cur--,pre--;
22     if(cur<=start)return false;
23
24     for(cur=end-1;*cur<=*pre;cur--);//找到逆序中大于*pre的元素的最小元素
25     swap(cur,pre);
26     reverse(pre+1,end);//将尾部的逆序变成正序
27     return true;
28 }
29
30 int main(){
31     char s1[]="01224",s2[]="8000";
32     reverse(s1,s1+strlen(s1));
33     printf("%s\n",s1);
34     int n=strlen(s2);
35     puts("下一个排列:");
36     int cnt=0;
37     do{
38         puts(s2);
39         cnt++;
40     }while(next_permutation(s2,s2+n));
41     printf("%d",cnt);
42 }

将上述代码的next_permutation 中的所有有关pre和cur指针内容比较的部分的符号反转,就得到了prev_nextpermutation

代码如下:

 1 bool prev_permutation(char *start,char *end){
 2     char *cur = end-1, *pre=cur-1;
 3     while(cur>start && *pre<=*cur)cur--,pre--;//这里符号有变化
 4     if(cur<=start)return false;
 5
 6     for(cur=end-1;*cur>=*pre;cur--);//这里符号有变化
 7     swap(cur,pre);
 8     reverse(pre+1,end);
 9     return true;
10 }

嗯,终于解决了心中的一大困惑,也希望能帮助各位~晚安~啦啦啦(~ ̄▽ ̄)~*

时间: 2024-10-05 13:27:44

STL next_permutation 算法原理和自行实现的相关文章

白话 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就是寻找这些元素所组成的数字的升序排列中的下一个数. 比较

打印全排列和stl::next_permutation

打印全排列是个有点挑战的编程问题.STL提供了stl::next_permutation完美的解决了这个问题. 但是,如果不看stl::next_permutation,尝试自己解决,怎么做? 很自然地,使用递归的办法: 1. 单个元素的排列只有1个. 2. 多个元素的排列可以转化为: 以每个元素为排列的首个元素,加上其他元素的排列. 有了思路,就可以编码了. 第一个版本: void printAllPermutations(const std::string& prefix, int set[

十三种基于直方图的图像全局二值化算法原理、实现、代码及效果(转)

十三种基于直方图的图像全局二值化算法原理.实现.代码及效果(转) http://www.cnblogs.com/carekee/articles/3643394.html 图像二值化的目的是最大限度的将图象中感兴趣的部分保留下来,在很多情况下,也是进行图像分析.特征提取与模式识别之前的必要的图像预处理过程.这个看似简单的问题,在过去的四十年里受到国内外学者的广泛关注,产生了数以百计的阈值选取方法,但如同其他图像分割算法一样,没有一个现有方法对各种各样的图像都能得到令人满意的结果. 在这些庞大的分

初探STL之算法

算法 STL算法部分主要由头文件<algorithm>,<numeric>,<functional>组成.要使用 STL中的算法函数必须包含头文件<algorithm>,对于数值算法须包含<numeric>,<functional>中则定义了一些模板类,用来声明函数对象. 分类 STL中算法大致分为四类: 1.非可变序列算法:指不直接修改其所操作的容器内容的算法. 2.可变序列算法:指可以修改它们所操作的容器内容的算法. 3.排序算法

SSH原理与运用(一)和(二):远程登录 RSA算法原理(一)和(二)

SSH原理与运用(一)和(二):远程登录  RSA算法原理(一)和(二) http://www.ruanyifeng.com/blog/2011/12/ssh_remote_login.html http://www.ruanyifeng.com/blog/2011/12/ssh_port_forwarding.html RSA算法原理(一) http://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html RSA算法原理(二)

STL之算法总结

STL之算法总结 STL算法部分主要由头文件<algorithm>,<numeric>,<functional>组成. 要使用 STL中的算法函数必须包含头文件<algorithm>, 对于数值算法须包含<numeric>,<functional>中则定义了一些模板类,用来声明函数对象. STL中算法大致分为四类: 1.非可变序列算法:指不直接修改其所操作的容器内容的算法. 2.可变序列算法:指可以修改它们所操作的容器内容的算法. 3

常见经典排序算法学习总结,附算法原理及实现代码(插入、shell、冒泡、选择、归并、快排等)

博主在学习过程中深感基础的重要,经典排序算法是数据结构与算法学习过程中重要的一环,这里对笔试面试最常涉及到的7种排序算法(包括插入排序.希尔排序.选择排序.冒泡排序.快速排序.堆排序.归并排序)进行了详解.每一种算法都有基本介绍.算法原理分析.算法代码. 转载请注明出处:http://blog.csdn.net/lsh_2013/article/details/47280135 插入排序 1)算法简介 插入排序(Insertion Sort)的算法描述是一种简单直观的排序算法.它的工作原理是通过

C++复习:STL之算法

算法 1算法基础 1.1算法概述 算法部分主要由头文件<algorithm>,<numeric>和<functional>组成. <algorithm>是所有STL头文件中最大的一个,其中常用到的功能范围涉及到比较.交换.查找.遍历操作.复制.修改.反转.排序.合并等等. <numeric>体积很小,只包括几个在序列上面进行简单数学运算的模板函数,包括加法和乘法在序列上的一些操作. <functional>中则定义了一些模板类,用以声

[C++ STL] 常用算法总结

1 概述 STL算法部分主要由头文件<algorithm>,<numeric>,<functional>组成.要使用 STL中的算法函数必须包含头文件<algorithm>,对于数值算法须包含<numeric>,<functional>中则定义了一些模板类,用来声明函数对象. 2 常用算法介绍 STL中算法大致分为四类: 非可变序列算法:指不直接修改其所操作的容器内容的算法. 可变序列算法:指可以修改它们所操作的容器内容的算法. 排序