STL中關於全排列next_permutation以及prev_permutation的用法

 這兩個函數都包含在algorithm庫中。STL提供了兩個用來計算排列組合關係的算法,分別是next_permutation和prev_permutation。c++

1、函數原型

首先咱們來看看這兩個函數的函數原型:算法

  • next_permutation:
1 template< class BidirIt >bool next_permutation( BidirIt first, BidirIt last );
2 template< class BidirIt, class Compare >bool next_permutation( BidirIt first, BidirIt last, Compare comp );

 

  • prev_permutation:
1 template< class BidirIt >bool prev_permutation( BidirIt first, BidirIt last);
2 template< class BidirIt, class Compare >bool prev_permutation( BidirIt first, BidirIt last, Compare comp);

1.參數

first,end ——從新排序的元素範圍數組

comp —— 自定義比較函數函數

顧名思義,next_permutation就是求下一個排列組合,而prev_permutation就是求上一個排列組合。首先咱們必須瞭解什麼是「下一個」排列組合,什麼是「前一個」排列組合。考慮由三個字符所組成的序列{a,b,c}。
spa

那麼按照字典序排升序他們一共有下面這幾種排列方式:code

  • abc
  • acb
  • bac
  • bca
  • cab
  • cba

若是給定排列方式P,令P爲{acb},那麼next_permutation即求P+1也就是{bac},prev_permutation也就是求P-1即爲{abc}。固然也能夠自定義謂詞函數進行自定義的「下一個排列組合」。blog

2、代碼演示

下面是示範代碼:排序

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 
 5 int main(){
 6     int a[] = {1,2,3};
 7     do{
 8         for(int i = 0; i < 3; i++) cout << a[i] << ' ';
 9         cout << endl;
10     }while(next_permutation(a,a+3));
11     
12     cout << endl;
13     do{
14         for(int i = 0; i < 3; i++) cout << a[i] << ' ';
15         cout << endl;
16     }while(prev_permutation(a,a+3));
17     return 0;
18 }

 

  預計上面代碼的運行結果應該是輸出兩組1,2,3的排列組合方式,共12行,但實際運行結果以下:原型

Why?it

咱們試着輸出第一個循環後a數組的排列方式,結果會獲得1 2 3,這是由於當next_permutation去找下一個排列組合P+1,找到則排列爲下一個排列組合返回true,不然數組變成字典序最小的排列組合(即爲重置排列)並返回false,prev_permutation也同理。

3、手動實現

咱們先想一想如何實現next_permutation,prev_permutation 的原理與之相似。根據字典序,若是排列爲正序既從小到大排列,是一組排列組合中最小的排列方式,而逆序既從大到小排列,則是一組排列組合中最大的排列方式。

在n個元素的排列全排列中,從給定排列P 求解下一排列P+1 ,假設兩個排列組合有前k位是相同的,那麼咱們只須要在後面n-k個元素的排列 P(n-k)中求得下一個排列便可。既然咱們須要的是後面 n-k位的排列,那麼直接從後向前考察。

例如排列 1 2 3 4 5,這是一個正序排列,所以全排列中最小的排列,記爲P.

如今要求P+1,設P1=P+1,P1是 1 2 3 5 4. 能夠發現P1的前三位和P的前三位徹底相同,惟一不一樣的是後兩位順序顛倒,最後兩位從正序的 4 5 變成了逆序的 5 4.

接着求P1+1.設P2 = P1+1,P2是 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 這個子排列是一個逆序排列。將逆序排列反向後,獲得的正序排列是所能造成的最小排列,所以,4 3 5 是4 爲首位元素所能造成最小排列,而前3 位沒有變化,故咱們獲得了下一排列P2.

另外,大於3的最小元素,即4 ,也是第一個大於3的元素,由於 5 4 是個逆序排列。

對於可重集排列 1 2 3 5 4 3 2 1也同理,首先找到第一個非逆序元素,這裏是3,而後從後向前尋找第一個大於3的元素,這裏是4,交換,獲得 4 5 3 3 2 1 的子排列,而後反向,便可獲得下一排列。若是沒有找到第一個非逆序元素,那麼說明該排列已是最大排列。

代碼:

 1 template<class T>
 2 bool next_permutation(T begin, T end){
 3     T i = end;
 4     if (begin == end || begin == --i) return false;
 5  
 6     while(1){
 7         T i1 = i,i2;
 8         if (*--i < *i1) {   //找第k位
 9             for(i2 = end; *i >= *i2; i2--);//從後往前找到逆序中大於*i的元素的最小元素
10             iter_swap(i, i2);
11             reverse(i1, end);////將尾部的逆序變成正序
12             return true;
13         }
14         if (i == begin) {
15             reverse(begin, end);
16             return false;
17         }
18     }
19 }

prev_permutation的代碼實現也相似,這裏再也不給出,各位能夠自行嘗試。

4、複雜度分析

最多n/2次交換,n爲區間長度,平均每次調用使用了3次比較和1.5次交換,時間複雜度爲O(n)。

相關文章
相關標籤/搜索