文章目錄
- 下面說一下全排列:
- 全排列遞迴演算法:
- STL的實現方法:
編程很多時候用到排列組合。。。
組合可以搜尋解決,也可以位元運算輕鬆解決。
比如有len個數字num[]儲存,求出所有組合。。。
那麼就可以這樣寫
for (int i = 1; i < (1 << len); ++ i)// get zuhe{int len1 = 0;for (int j = 0; j < len; ++ j){if (i & (1 << j)){zuhe[len1 ++] = num[j];}}}
實際上就是有那個數那麼就有n個對應的位,1 1 1 1 1。。。。這些位可以為0或者1兩種情況,那麼從1 到2^n-1不正是代表了所有種情況嗎?然後在求為1位提出來就okle。
下面說一下全排列:
首先C++ STL直接調用next_permutation,注意這個函數是有順序的,從小到大,比如123,如果初始化為321,那麼下一個就沒了,已經到最後了。如果你想要獲得所有的,那麼初始化必須是123.。。。
用法就是zuhe[] ; 數組長度len1,初始化好序列。。。
do {輸出zuhe。。。。
} while (next_permutation(zuhe, zuhe + len1));//qu pai lie
全排列遞迴演算法:
所謂全排列,就是將集合中元素的所有排列情況依次輸出。比如{1、2、3}的全排列為:123、132、213、231、312、321,共6種,滿足計算公式N!(N為集合中元素個數,不重複)。
當元素不重複時,全排列採用遞迴思想較容易實現,它的遞迴公式推導步驟類似:
1、要求得123的全排列,只需求得:1並上23的全排列(1 23, 1 32),2並上13的全排列(2 13, 2 31),3並上12的全排列(3 12 321)。
2、對於23的全排列,只需求得2並上3的全排列,3並上2的全排列。步驟1中13、12的全排列也類似。
3、對於3的全排列或者2的全排列,就是它們的本身。遞迴結束。
遞迴實現不重複元素全排列演算法的實現代碼(C++)如下:
//交換a和bvoid Swap(int *a, int *b){ int t = *a; *a = *b; *b = t;} //全排列函數。list:待排元素列表,start:起始位置下標,end:最後一個有效元素的下一個下標。void Permutation(int start, int end, int list[]){ int i; if (start >= end) //遞迴結束,列印當前這次全排列結果,返回。 { for (i = 0; i < end; i++) { printf("%d ", list[i]); } printf("\n"); return; } //對於給定的list[start...end],要使區間中每一個元素都有放在第一位的機會, //然後開始遞迴調用自身,得到list[start+1...end]的全排列。 for (i = start; i < end; i++) { Swap(&list[i], &list[start]); //交換元素,使每一個元素都有放在第一位的機會。 Permutation(start+1, end, list); //遞迴調用 Swap(&list[i], &list[start]); //恢複原始的list,不影響下次遞迴調用。 }}
上述程式的調用方法為:
#include <iostream>using namespace std; int main(){ int a[] = {1, 2, 3}; Permutation(0, 2, a); return 0;}
STL的實現方法:
當待排元素列表含有重複項時,上述演算法就需要改進,其中一種方法可以是維護一個存放不重複排列的集合,每次新產生1個排列,如果集合中不存在這個排列,則插入排列,否則,放棄。
要實現含重複元素的全排列演算法,可以參考STL中next_premutation()函數的實現方法(在algorithm.h中聲明)。該函數會將列表中元素按字典序(wiki)給出全排列中的下一個排列,它的實現演算法為:
令當前排列為P(0)P(1)P(2)...P(n-1)P(n)。則求它下一個排列的過程為,
1、從後往前遍曆,找到第一個P(i)>P(i-1)的元素,記錄下標i。比如排列1、5、2、4、3中滿足條件的元素為4,記下它的下標i = 3,因為P(i)是4,P(i-1)是2,滿足P(i)>P(i-1)。如果找不到這樣的i,則表示該序列已經是字典序中的最後一個序列,結束演算法。
2、從後往前遍曆,找到第一個P(j)>P(i-1)的數,記錄下標k。還是上面這個例子,P(i-1)為2,從後往前第一個大於P(i-1)是P(4)=3,因此記錄下j=4。
3、互換P(i-1)和P(j),得到新序列1、5、3、4、2。
4、將P[i...n]間的元素逆置,返回序列。上述例子中為逆置4和2,得到最終的序列1、5、3、2、4。
用比較通俗的例子解釋一下上述步驟:
假設現在有一個序列4、6、5、3、2、1,要求得字典序的下一個序列。首先,從後往前找到第一個i,使得P(i)>P(i-1),明顯這裡i是1,P(i)=6,這個意思是,在6之後的元素,都是按值遞減的,否則第一步求i的時候也不會找到第2個元素6才滿足條件。現在知道,從i開始到最後,其實是字典序裡的最大序列了(一直按值遞減)。第二步,拿出i的前一個元素P(i-1)=4,將它與原序列從後往前第一個大於它的元素交換位置,這裡這個與4交換的元素是5,這樣序列就變成了5、6、4、3、2、1,至此,最高位升了一級(4->5),接著要把低位的從最大變成最小(就像199之後是200,最高為從1變成2後,要把低位從最大99變成最小00),這裡的低位是最大序列6、4、3、2、1,變成最小序列只需逆置即可,變成1、2、3、4、6,原序列變為5、1、2、3、4、6,即為所求。
實現代碼如下:
/** *如果存在當前序列在字典序中的下一個排列,則返回true, *否則返回false。 */bool next_premutation(int list[], int length){ int i, j; //步驟1:得到i。 for (i = length - 1; i > 0; i--) { if (list[i] > list[i-1]) { break; //記下下標i。 } } if (i <= 0) { //表示當前排列已經是字典序中的最後一個序列,沒有下一個了。 return false; } //步驟2:得到j。 for (j = length - 1; j > 0 ; j--) { if (list[j] > list[i-1]) { break; //記下下標j。 } } //步驟3:互換list[i-1]和list[j]。 int temp = list[i-1]; list[i-1] = list[j]; list[j] = temp; //步驟4:逆置list[i...n]。 int start, end; for (start = i, end = length-1; start < end; start++, end--) { int temp = list[start]; list[start] = list[end]; list[end] = temp; } return true;}
採用這種方法要獲得一個集合的全排列,可按下面方法調用(和stl函數next_permutation()的調用方法基本一致):
#include <iostream>
using namespace std;
int main()
{
int list[] = {1, 2, 3, 4, 5};
do
{
for (int i = 0; i < 5; i++)
{
printf("%d ", list[i]);
}
printf("\n");
}while (next_premutation(list, 5));
return 0;
}