問題來自《Linux C一站式編程》,是個挺有意思的題目。算法
二、定義一個數組,編程打印它的全排列。好比定義:編程
#define N 3 int a[N] = { 1, 2, 3 };則運行結果是:數組
$ ./a.out 1 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種排列)。code完成了上述要求以後再考慮第二個問題:若是再定義一個常量M表示從N個數中取幾個數作排列(N==M時表示全排列),原來的程序應該怎麼改?遞歸
最後再考慮第三個問題:若是要求從N個數中取M個數作組合而不是作排列,就不能用原來的遞歸過程了,想一想組合的遞歸過程應該怎麼描述,編程實現它。io
不考慮數組元素相同的狀況,咱們能夠按照題目提供的思路寫出以下代碼:變量
#include <stdio.h> #define N 3 int 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<=N void 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個數,只有保持這樣的偏序關係才能保證不會重複組合。