本程序的思路是開一個數組,其下標表示1到m個數,數組元素的值爲1表示其下標
表明的數被選中,爲0則沒選中。
首先初始化,將數組前n個元素置1,表示第一個組合爲前n個數。
而後從左到右掃描數組元素值的「10」組合,找到第一個「10」組合後將其變爲
「01」組合,同時將其左邊的全部「1」所有移動到數組的最左端。
當第一個「1」移動到數組的m-n的位置,即n個「1」所有移動到最右端時,就得
到了最後一個組合。
例如求5中選3的組合: python
1 1 1 0 0 //1,2,3 1 1 0 1 0 //1,2,4 1 0 1 1 0 //1,3,4 0 1 1 1 0 //2,3,4 1 1 0 0 1 //1,2,5 1 0 1 0 1 //1,3,5 0 1 1 0 1 //2,3,5 1 0 0 1 1 //1,4,5 0 1 0 1 1 //2,4,5 0 0 1 1 1 //3,4,5
代碼實現算法
#define INIT_TAT(TAT) TAT = (0L) /*STATUS INIT*/ #define SET_TAT(TAT, FLAG) TAT = ((TAT)|(FLAG)) /*STATUS SET*/ #define CLEAN_TAT(TAT, FLAG) TAT = ((TAT)&(~(FLAG))) /*STATUS CLEAN*/ #define CHECK_TAT(TAT, FLAG) ((FLAG) == ((TAT)&(FLAG))) /*STATUS CHECK*/ #define SET_POS(a, b) SET_TAT (a[b>>5], (1 << (b & 0x1f))) #define CLEAN_POS(a, b) CLEAN_TAT(a[b>>5], (1 << (b & 0x1f))) #define CHECK_POS(a, b) CHECK_TAT(a[b>>5], (1 << (b & 0x1f))) static void _combine2(int *array, int *record, const int m, const int n, const int M); /** * @ brief 輸出對輸入集合 m 選 n 的全部組合 * @ param[in] array 輸入集合的頭指針 * @ param[in] m 輸入集合中欲選取的數字個數 * @ param[in] n 輸入集合的數字個數 * @ note 無輸入檢測, 輸入集合最少兩個數 * @TODO 開銷計算 */ void combine1(int *array, const int m, const int n) { int i, pos; /*pos 用來存放1*/ unsigned int *tarray; int status; /*三種狀態轉換, 0 初始狀態, 1.已掃描到一 2. 在一狀態中掃描到0*/ tarray = (unsigned int *)malloc( ( (n >> 5) +\ (n & 0x1f ? 1 : 0) )* sizeof( unsigned int)); /*計算須要的臨時表大小*/ memset(tarray, 0, n); for(i = 0; i < m; i++){ SET_POS(tarray, i); printf("%d ", array[i]); } printf("\n"); while(1){ status = 0; for(i = 0, pos = 0; i < n; i++){ if(!CHECK_POS(tarray, i)){ if(1 == status){ /*找到 1, 0的組合*/ status = 2; SET_POS(tarray, i); CLEAN_POS(tarray, pos - 1); break; } }else{ /*找到1*/ status = 1; CLEAN_POS(tarray, i); SET_POS(tarray, pos); pos++; } } if(2 == status){ for(i = 0; i < n; i++) if(CHECK_POS(tarray, i)) printf("%d ", array[i]); printf("\n"); }else{ break; } } }
儘管排列組合是生活中常常遇到的問題,可在程序設計時,不深刻思考或者經驗不足都讓人無從下手。因爲排列組合問題老是先取組合再排列,而且單純的排列問題相對簡單,因此本文僅對組合問題的實現進行詳細討論。以在n個數中選取m(0<m<=n)個數爲例,問題可分解爲:
1. 首先從n個數中選取編號最大的數,而後在剩下的n-1個數裏面選取m-1個數,直到從n-(m-1)個數中選取1個數爲止。
2. 從n個數中選取編號次小的一個數,繼續執行1步,直到當前可選編號最大的數爲m。
很明顯,上述方法是一個遞歸的過程,也就是說用遞歸的方法能夠很乾淨利索地求得全部組合。
下面是遞歸方法的實現:數組
求從數組a[1..n]中任選m個元素的全部組合。spa
a[1..n]表示候選集,m表示一個組合的元素個數。.net
b[1..M]用來存儲當前組合中的元素, 常量M表示一個組合中元素的個數。設計
代碼實現指針
/** * @ brief 輸出對輸入集合 m 選 n 的全部組合 * @ param[in] array 輸入集合的頭指針 * @ param[in] m 輸入集合中欲選取的數字個數 * @ param[in] n 輸入集合的數字個數 * @ note 無輸入檢測, 輸入集合最少兩個數 * @TODO 開銷計算 * */ void combine2(int *array, const int m, const int n){ int *tarray = (int *)malloc(m * sizeof(int)); _combine2(array, tarray, m, n, m); free(tarray); } void _combine2(int *array, int *record, const int m, const int n, const int M){ int i; for(i = n; i >= m; i--){ record[m - 1] = i - 1; if( m > 1){ _combine2(array, record, m - 1, i - 1, M); }else{ for(i = M - 1; i >= 0; i--) printf("%d ", array[record[i]]); printf("\n") ; } } }
遞歸算法
全排列是將一組數按必定順序進行排列,若是這組數有n個,那麼全排列數爲n!個code
現以{1, 2, 3, 4, 5}爲例說明如何編寫全排列的遞歸算法。blog
首先看最後兩個數4, 5。 它們的全排列爲4 5和5 4, 即以4開頭的5的全排列和以5開頭的4的全排列。因爲一個數的全排列就是其自己,從而獲得以上結果。遞歸
再看後三個數3, 4, 5。它們的全排列爲3 4 五、3 5 四、 4 3 五、 4 5 三、 5 3 四、 5 4 3 六組數。即以3開頭的和4,5的全排列的組合、以4開頭的和3,5的全排列的組合和以5開頭的和3,4的全排列的組合.從而能夠推斷,設一組數p = {r1, r2, r3, ... ,rn}, 全排列爲perm(p),pn = p - {rn}。
所以perm(p) = r1perm(p1), r2perm(p2), r3perm(p3), ... , rnperm(pn)。當n = 1時perm(p} = r1。
爲了更容易理解,將整組數中的全部的數分別與第一個數交換,這樣就老是在處理後n-1個數的全排列。
代碼實現
/** * @ brief 對輸入集合進行全排列 * @ param[in] array 輸入集合的頭指針 * @ param[in] spos 輸入集合中首個元素idx * @ param[in] epos 輸入集合的最後一個idx * @ note 無輸入檢測 * cost mem : 0 * cost time : n! */ void _perm(int *array, const int spos, const int epos) { int i; if(spos == epos){ for(i = 0; i <= epos; i++) printf("%d ", array[i]); printf("\n"); }else{ for(i = spos; i <= epos; i++){ swap3(array + i, array + spos); _perm(array, spos + 1, epos); swap3(array + i, array + spos); } } }
http://blog.csdn.net/todototry/article/details/1403807