所謂全排列,就是列印出字串中所有字元的所有排列。例如輸入字串abc
,則列印出 a、b、c 所能排列出來的所有字串abc
、acb
、bac
、bca
、cab
和cba
。
一般最先想到的方法是暴力迴圈法,即對於每一位,遍歷集合中可能的元素,如果在這一位之前出現過了該元素,跳過該元素。例如對於abc
,第一位可以是 a 或 b 或 c 。當第一位為 a 時,第二位再遍歷集合,發現 a 不行,因為前面已經出現 a 了,而 b 和 c 可以。當第二位為 b 時 , 再遍歷集合,發現 a 和 b 都不行,c 可以。可以用遞迴或迴圈來實現,但是複雜度為 $o(n^n)$ 。有沒有更優雅的解法呢。
首先考慮bac
和cba
這二個字串是如何得出的。顯然這二個都是abc
中的 a 與後面兩字元交換得到的。然後可以將abc
的第二個字元和第三個字元交換得到acb
。同理可以根據bac
和cba
來得bca
和cab
。
因此可以知道全排列就是從第乙個數字起每個數分別與它後面的數字交換,也可以得出這種解法每次得到的結果都是正確結果,所以複雜度為 o(n!)。找到這個規律後,遞迴的**就很容易寫出來了:
#include#include
//交換兩個字元
void
swap
(char *a ,char *b)
//遞迴全排列,start 為全排列開始的下標, length 為str陣列的長度
void
allrange
(char* str,int start,int length)
else}}
void
permutation
(char* str)
void
main
()
為了得到不一樣的排列,可能我們最先想到的方法是當遇到和自己相同的就不交換了。如果我們輸入的是abb
,那麼第乙個字元與後面的交換後得到bab
、bba
。然後abb
中,第二個字元和第三個就不用交換了。但是對於bab
,它的第二個字元和第三個是不同的,交換後得到bba
,和之前的重複了。因此,這種方法不行。
因為abb
能得到bab
和bba
,而bab
又能得到bba
,那我們能不能第乙個bba
不求呢? 我們有了這種思路,第乙個字元a
與第二個字元b
交換得到bab
,然後考慮第乙個字元a
與第三個字元b
交換,此時由於第三個字元等於第二個字元,所以它們不再交換。再考慮bab
,它的第二個與第三個字元交換可以得到bba
。此時全排列生成完畢,即abb
、bab
、bba
三個。
這樣我們也得到了在全排列中去掉重複的規則:去重的全排列就是從第乙個數字起每個數分別與它後面非重複出現的數字交換。用程式設計的話描述就是第i個數與第j個數交換時,要求[i,j)中沒有與第j個數相等的數。下面給出完整**:
#include#include
//交換兩個字元
void
swap
(char *a ,char *b)
//在 str 陣列中,[start,end) 中是否有與 str[end] 元素相同的
bool
isswap
(char* str,int start,int end)
return
true;
}//遞迴去重全排列,start 為全排列開始的下標, length 為str陣列的長度
void
allrange2
(char* str,int start,int length)
else}}
}void
permutation
(char* str)
void
main
()
如果不是求字元的所有排列,而是求字元的所有組合應該怎麼辦呢?還是輸入三個字元 a、b、c,則它們的組合有a
b
c
ab
ac
bc
abc
。當然我們還是可以借鑑全排列的思路,利用問題分解的思路,最終用遞迴解決。不過這裡介紹一種比較巧妙的思路 —— 基於位圖。
假設原有元素 n 個,則最終組合結果是 $2^n-1$ 個。我們可以用位操作方法:假設元素原本有:a,b,c 三個,則 1 表示取該元素,0 表示不取。故取a
則是001
,取ab
則是011
。所以一共三位,每個位上有兩個選擇 0 和 1。而000
沒有意義,所以是$2^n-1$個結果。
這些結果的位圖值都是 1,2…2^n-1。所以從值 1 到值 $2^n-1$ 依次輸出結果:
001
,010
,011
,100
,101
,110
,111
。對應輸出組合結果為:a
,b
,ab
,c
,ac
,bc
,abc
。
因此可以迴圈 1~2^n-1,然後輸出對應代表的組合即可。有**如下:
#include#include
void
combination
(char *str)
}printf("\n");}}
void
main
()
#include #include void combine(char s, int n, int m, char * subset, int sub_len)
for(int i = n; i >= m; i--)
}int main()
else
}return 0;
}
-eof-
全排列和全組合實現
所謂全排列,就是列印出字串中所有字元的所有排列。例如輸入字串abc,則列印出 a b c 所能排列出來的所有字串abc acb bac bca cab和cba。一般最先想到的方法是暴力迴圈法,即對於每一位,遍歷集合中可能的元素,如果在這一位之前出現過了該元素,跳過該元素。例如對於abc,第一位可以是...
全排列和全組合實現
記得 老趙之前在微博上吐槽說,有的人真是毫無長進,六年前某同事不會寫程式輸出全排列,昨天發郵件還是問我該怎麼寫,這時間浪費到我都看不下去了。那時候就很好奇全排列到底是什麼東西,到底有多難?今天覆習的時候終於碰到這題了,結果果然自己太渣,看了好久都沒明白,實現又是磕磕碰碰的。所以,就把它整理成筆記加深...
全排列和全組合的實現
所謂全排列,就是列印出字串中所有字元的所有排列。例如輸入字串abc,則列印出 a b c 所能排列出來的所有字串abc acb bac bca cab和cba。一般最先想到的方法是暴力迴圈法,即對於每一位,遍歷集合中可能的元素,如果在這一位之前出現過了該元素,跳過該元素。例如對於abc,第一位可以是...