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 顯然不是逆序
我們現在要在 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 算法原理和自行實現