標籤:style blog color io for sp div 問題 log
問題來自《Linux C一站式編程》,是個挺有意思的題目。
2、定義一個數組,編程列印它的全排列。比如定義:
#define N 3 int a[N] = { 1, 2, 3 };
則運行結果是:
$ ./a.out1 2 3 1 3 2 2 1 3 2 3 1 3 2 1 3 1 2
程式的主要思路是:
- 把第1個數換到最前面來(本來就在最前面),準備列印1xx,再對後兩個數2和3做全排列。
- 把第2個數換到最前面來,準備列印2xx,再對後兩個數1和3做全排列。
- 把第3個數換到最前面來,準備列印3xx,再對後兩個數1和2做全排列。
可見這是一個遞迴的過程,把對整個序列做全排列的問題歸結為對它的子序列做全排列的問題,注意我沒有描述Base Case怎麼處理,你需要自己想。
你的程式要具有通用性,如果改變了N和數組a的定義(比如改成4個數的數組),其它代碼不需要修改就可以做4個數的全排列(共24種排列)。
完成了上述要求之後再考慮第二個問題:如果再定義一個常量M表示從N個數中取幾個數做排列(N==M時表示全排列),原來的程式應該怎麼改?
最後再考慮第三個問題:如果要求從N個數中取M個數做組合而不是做排列,就不能用原來的遞迴過程了,想想組合的遞迴過程應該怎麼描述,編程實現它。
不考慮數組元素相同的情況,我們可以按照題目提供的思路寫出如下代碼:
#include <stdio.h>#define N 3int a[N];void perm(int); /*求數組的全排列 */void print();void swap(int, int);int main(){ int i; for(i = 0; i < N; ++i){ a[i] = i + 1; } perm(0);}void perm(int offset){ int i, temp; if(offset == N-1){ // BaseCase print(); return; }else{ for(i = offset;i < N; ++i){ swap(i, offset);//交換首碼 perm(offset + 1);//遞迴 swap(i, offset);//將首碼換回來,繼續做前一次排列 } }}void print(){ int i; for(i = 0; i < N; ++i) printf(" %d ",a[i]); printf("\n");} void swap(int i, int offset){ int temp; temp = a[offset]; a[offset] = a[i]; a[i] = temp;}
如果平常遞迴寫的不多的話,這段代碼還是很容易寫錯的(沒錯,我就是在說我自己)。
在perm函數遞迴調用自己之後記得把元素位置交換回去,保證回溯時條件一致。
然後看第二個問題,這是更加一般的排列。仔細觀察上面的代碼,把特殊推導到一般,主要修改如下(用注釋符標出):
#include <stdio.h>#define N 4#define M 2 // 取出M個元素進行排列,預設M<=Nvoid print(){ int i; for(i = 0; i < M; ++i) // N->M,列印a裡前M個排列元素 printf(" %d ",a[i]); printf("\n"); }void perm(int offset){ int i; if(offset == M){ // N->M,排列到M個數時遞迴到達BaseCase print(); return; }else{ for(i = offset;i < N; ++i){ swap(i, offset); perm(offset + 1); swap(i, offset); } } }
再來看組合,同樣用要求用遞迴解決,如果相關概念沒有搞得很清楚,加上上面寫的排列的代碼,很容易寫不出來(沒錯,說的還是我),然而代碼其實很簡單,不過遞迴確實更加複雜。
void comb(int n, int m){ int i; if (m == 0) { print(); return; } else { for (int i = n-1; i >= 0; --i) { b[m-1] = a[i]; comb(i, m-1); } }}
複雜之處在於,排列都是(n->n-1)這樣的遞迴,然而組合這裡是(n->i,m->m-1)這樣非規律的遞迴調用,因為i是個變數。
但是組合的演算法描述很簡單,假設有一個兩兩元素互不相同的N長數組a,從數組尾端依次取M個數(b1,b2,···,bm)成為一個組合,並且滿足條件:如果i>j,那麼bi在a中的下標一定小於bj。
簡單來說,就是從後往前取數組裡的M個數,只有保持這樣的偏序關係才能保證不會重複的群組合。
如何用C表示排列組合?