1. 程式人生 > >STL next_permutation 算法原理和自行實現

STL next_permutation 算法原理和自行實現

start 可能 per str 求解 觀察 字符串 成了 printf

目標

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 }
技術分享圖片

STL next_permutation 算法原理和自行實現