码迷,mamicode.com
首页 > 其他好文 > 详细

STL具体操作之next_permutation和prev_permutation函数

时间:2015-07-31 09:08:41      阅读:126      评论:0      收藏:0      [点我收藏+]

标签:



next函数默认的是从小到大的顺序,pre函数默认的是从大到小的顺序;
{3,1,2}用next得到的结果是{3,1,2}和{3,2,1};
      用pre得到的结果是{3,1,2},{2,3,1},{2,1,3},{1,3,2,},{1,2,3};
原理如下:
【STL】next_permutation的原理和使用 
1、碰到next_permutation(permutation:序列的意思)
今天在TC上碰到一道简单题(SRM531 - Division Two - Level One),是求给定数组不按升序排列的最小字典序列(Sequence of numbers A is lexicographically smaller than B if A contains a smaller number on the first position on which they differ)。
解法很简单,就是将数组排序(升序),然后从尾到头找到第一个可以交换的位置(因为可能有重复的数字)。
最后看别人的解法,排序后,用了STL中的一个函数next_permutaion,直接求到第一个不按升序排列的序列。
2、next_permutation实现原理
在《STL源码解析》中找到了这个函数,在此也简单叙述一下原理:
在STL中,除了next_permutation外,还有一个函数prev_permutation,两者都是用来计算排列组合的函数。前者是求出下一个排列组合,而后者是求出上一个排列组合。所谓“下一个”和“上一个”,书中举了一个简单的例子:对序列 {a, b, c},每一个元素都比后面的小,按照字典序列,固定a之后,a比bc都小,c比b大,它的下一个序列即为{a, c, b},而{a, c, b}的上一个序列即为{a, b, c},同理可以推出所有的六个序列为:{a, b, c}、{a, c, b}、{b, a, c}、{b, c, a}、{c, a, b}、{c, b, a},其中{a, b, c}没有上一个元素,{c, b, a}没有下一个元素。
next_permutation的函数原型如下:
Cpp代码 复制代码 收藏代码
1.template<class BidirectionalIterator> 
2.bool next_permutation( 
3.      BidirectionalIterator _First,  
4.      BidirectionalIterator _Last 
5.); 
6.template<class BidirectionalIterator, class BinaryPredicate> 
7.bool next_permutation( 
8.      BidirectionalIterator _First,  
9.      BidirectionalIterator _Last, 
10.      BinaryPredicate _Comp 
11. ); 
对于第二个重载函数的第三个参数,默认比较顺序为小于。如果找到下一个序列,则返回真,否则返回假。
函数实现原理如下:
在当前序列中,从尾端往前寻找两个相邻元素,前一个记为*i,后一个记为*ii,并且满足*i < *ii。然后再从尾端寻找另一个元素*j,如果满足*i < *j,即将第i个元素与第j个元素对调,并将第ii个元素之后(包括ii)的所有元素颠倒排序,即求出下一个序列了。
代码实现如下:
Cpp代码 复制代码 收藏代码
1.template<class BidirectionalIterator> 
2.bool next_permutation( 
3.      BidirectionalIterator first,  
4.      BidirectionalIterator last 
5.) 
6.{ 
7.    if(first == last) 
8.        return false; //空序列 
9. 
10.    BidirectionalIterator i = first; 
11.    ++i; 
12.    if(i == last) 
13.        return false;  //一个元素,没有下一个序列了 
14.     
15.    i = last; 
16.    --i; 
17. 
18.    for(;;) { 
19.        BidirectionalIterator ii = i; 
20.        --i; 
21.        if(*i < *ii) { 
22.            BidirectionalIterator j = lase; 
23.            while(!(*i < *--j)); 
24. 
25.            iter_swap(i, j); 
26.            reverse(ii, last); 
27.            return true; 
28.        } 
29.         
30.        if(i == first) { 
31.            reverse(first, last);  //全逆向,即为最小字典序列,如cba变为abc 
32.            return false; 
33.        } 
34.    } 
35. 
36.} 
prev_permutation实现类似,就是反向查找
3、使用next_permutation
思考问题,序列{a, d, c, e, b}的下一个序列是什么呢?请利用前面的分析推出答案,并用代码验证。
我这里分别用数组和vector来表示序列,用next_permutation得到下一个序列(编译环境:Dev-C++):
Cpp代码 复制代码 收藏代码
1.#include <cstdlib> 
2.#include <iostream> 
3.#include <algorithm> 
4.#include <vector>   
6.using namespace std;  
8.void TestArray()  
9.{ 
10.    char chs[] = {‘a‘, ‘d‘, ‘c‘, ‘e‘, ‘b‘}; 
11.    int count = sizeof(chs)/sizeof(char);       
13.    next_permutation(chs+0, chs + count);     
15.    printf("TestArray:\n"); 
16.    for(int i = 0; i < count; i++) { 
17.            printf("%c\t", chs[i]); 
18.    }     
20.    printf("\n"); 
21.}  
23.void TestVector() 
24.{ 
25.     char chs[] = {‘a‘, ‘d‘, ‘c‘, ‘e‘, ‘b‘}; 
26.     int count = sizeof(chs)/sizeof(char); 
27.     vector<char> vChs(chs, chs + count);       
29.     next_permutation(vChs.begin(), vChs.end());       
31.     printf("TestVector:\n"); 
32.     vector<char>::iterator itr; 
33.     for(itr = vChs.begin(); itr != vChs.end(); itr++) { 
34.             printf("%c\t", *itr); 
35.     } 
36.     printf("\n"); 
37.}   
39.int main(int argc, char *argv[]) 
40.{ 
41.    TestArray(); 
42.    printf("\n"); 
43.    TestVector();       
45.    system("PAUSE"); 
46.    return EXIT_SUCCESS; 
47.} 
4、小结
用next_permutation和prev_permutation求排列组合很方便,但是要记得包含头文件#include <algorithm>。
虽然最后一个排列没有下一个排列,用next_permutation会返回false,但是使用了这个方法后,序列会变成字典序列的第一个,如cba变成abc。prev_permutation同理。
全排列问题的STL用法(next_permutation类)
标准库全排列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)表示将被替换,再次重后往前搜索第一个不小于X1的数据,标记为X2。交换X1,X2,然后把[PX+1,last)标记范围置逆。完成。
要点:为什么这样就可以保证得到的为最小递增。
从位置first开始原数列与新数列不同的数据位置是PX,并且新数据为X2。[PX+1,last)总是递减的,[first,PX)没有改变,因为X2>X1,所以不管X2后面怎样排列都比原数列大,反转[PX+1,last)使此子数列(递增)为最小。从而保证的新数列为原数列的字典序排列next。
明白了这个原理后,看下面例子:
int main(){
int a[] = {3,1,2};
do{
      cout << a[0] << " " << a[1] << " " << a[2] << endl;
}
while (next_permutation(a,a+3));
return 0;
}
输出:312/321          因为原数列不是从最小字典排列开始。
所以要想得到所有全排列
int a[] = {3,1,2};    change to   int a[] = {1,2,3};
另外,库中另一函数prev_permutation与next_permutation相反,由原排列得到字典序中上一次最近排列。
所以
int main(){
int a[] = {3,2,1};
do{
      cout << a[0] << " " << a[1] << " " << a[2] << endl;
}
while (prev_permutation(a,a+3));
return 0;
}
才能得到123的所有排列。
---------------------------------------------------------------------------------------------------------------------------------------------
上周见到了一道题,实现可输入任意字符串,可给出其所有可能排列组合的情况。想了半天,用自己所了解的知识都是处理不了(当然长久不用,很生疏了,再加之水平本就不高),在网上搜搜,得出了结果,贴出解决方法来,不太跟得上时代发展的同志们可以借鉴一下。 其实也并没有多难,现在C++语言中提供了现成的算法来解决排列组合问题,它们分别是next_permutation 和prev_permutation ,需要注意的是 "如果要走遍所有的排列,你必须先将元素排序"。 以下为转载: <<C++标准程序库>>这本书,在看到"变序性算法"部分的时候,发现两个函数next_permutation, prev_permutation对于我们平时处理排列组合的问题很有帮助,根据书上的介绍写了两个个测试函数:
void func1()
{
    vector<int> v;
    INSERT_ELEMENTS(v, 1,3);
    PRINT_ELEMENTS( v, "myself: ");
    while( next_permutation( v.begin(), v.end() ) )
    {
        PRINT_ELEMENTS( v, "");
    }
}
void func2()
{
    vector<int> v;
    INSERT_ELEMENTS(v, 1,3);
    PRINT_ELEMENTS( v, "myself: ");
    sort(v.begin(), v.end(), greater<int>() ); //增加排序(降序)

    while( prev_permutation( v.begin(), v.end() ) )
    {
        PRINT_ELEMENTS( v, "");
    }
}
如果以后再遇到类似问题,我们就如此如此,不用再费脑筋,人家有现成的函数,直接拿来用就是了。
外普及一下:
面这段才是真真的算法,STL里面的源码
  template inline
   bool next_permutation(_BidIt _First, _BidIt _Last)
   { // permute and test for pure ascending, using operator<
   _BidIt _Next = _Last;
   if (_First == _Last || _First == --_Next)
   return (false); 
   for (; ; )
   { // find rightmost element smaller than successor
   _BidIt _Next1 = _Next;
   if (*--_Next < *_Next1)
   { // swap with rightmost element that‘s smaller, flip suffix
   _BidIt _Mid = _Last;
   for (; !(*_Next < *--_Mid); )
   ;
   std::iter_swap(_Next, _Mid);
   std::reverse(_Next1, _Last);
   return (true);
   }  
   if (_Next == _First)
   { // pure descending, flip all
   std::reverse(_First, _Last);
   return (false);
   }
   }
   }
Ps: "STL":Standard Template Library,标准模板库(摘录bbs.csdn.net)
这是最早由Alexander Stepanov和Meng Lee(好像是华人的名字哦)完成,于1994年提交给ANSI/ISO 标准C++委员会并通过而成为标准C++的一部分。望文生义即可知这是一个代码库标准,不是语法标准。简单地说,STL是以C++中的模板语法为基础建立起来的一套包含基础数据结构和算法的代码库。STL的特点是实现了“类型参数化”,即STL的代码中可处理任意自定义类型的对象,如果不使用模板技术的话,这是一件相当困难的事。也因为这个原因,在最新的java及C#语法中均加入了对模板语法的支持,可见其重要性。另外一个有关STL重要的话题是GP(Generic Programming),泛型。这是与面向对象相并列的另外的一个编程模型,它以模板为基础,弱化了实体类型的差异,简化了编程时问题抽象的模型,提供了更好的封装性和弹性,对于繁杂的面向对象编程毫无疑问是一种解脱,至少是精神上的。GP是最近几年软件架构的一个研究热点,但国内真正的应用似乎并不多见。

版权声明:本文为博主原创文章,未经博主允许不得转载。

STL具体操作之next_permutation和prev_permutation函数

标签:

原文地址:http://blog.csdn.net/yueloveme/article/details/47164529

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!