遞歸的思想在寫程序中運用較為廣泛,看視很復雜的問題,通常通過遞歸思想找出“遞歸結構”,分解成重復的小步驟即可解決,但是遞歸的思想有時並不好理解(大佬,悟性高的忽略)。本文通過介紹全排序例子介紹遞歸思想,最后給出前一次博客寫的”坑爹的奧數“問題進行遞歸優化,給出執行時間。
一、問題描述:假如有編號為1、2、3的3張撲克牌和編號為1、2、3的3個盒子。現在需要將這3張撲克牌分別放到3個盒子里面,並且每個盒子有且只有一張撲克牌。那么一共有多少種不同的放法呢?
當有n個數字n個盒子(每個數字取值1~n之間且不重復),那么有多少種不同的放法呢?
輸入:n ------- 表示n個盒子
輸出:全排序 和 排序總數
二、代碼示例(帶有詳細注釋)
1 #include <stdio.h> 2 3 int a[10]={0},book[10]={0},n; 4 5 void dfs(int step) // step表示現在站在第幾個盒子面前 6 { 7 int i; 8 if(step==n+1) // 如果站在第n+1個盒子面前,則表示前n個盒子已經放好撲克牌 9 { 10 // 輸出一種排序(1~n號盒子的撲克牌編號) 11 for(i=1;i<=n;++i) 12 printf("%d",a[i]); 13 printf("\n"); 14 } 15 16 // 此時站在第step個盒子面前,應該放哪張牌呢? 17 // 按照1,2,3,。。。,n的順序一一嘗試 18 for(i=1;i<=n;++i) 19 { 20 // 判斷撲克牌i是否還在手上 21 if(book[i]==0) // book[i]等於0表示i號撲克牌在手上 22 { 23 // 開始嘗試使用撲克牌 24 a[step] = i; // 將i號撲克牌放入到第step個盒子中 25 book[i] = 1; // 將book[i]設為1,表示i號撲克牌已經不再手上 26 27 // 第step個盒子已經放好撲克牌沒接下來需要走到下一個盒子面前 28 dfs(step+1); // 這里通過函數的遞歸調用來實現(自己調用自己) 29 book[i] = 0; // 這是非常關鍵的一步,一定要將剛才嘗試的撲克牌收回,才能進行下一次嘗試 30 } 31 } 32 return; 33 } 34 35 /* 36 深度優先搜索的基本模型 37 void dfs(int step) 38 { 39 判斷邊界 40 嘗試每一種可能 41 for(i=1;i<=n;++i) 42 { 43 繼續下一步 dfs(step+1); 44 } 45 返回 return ; 46 } 47 */ 48 49 int main() 50 { 51 printf("請輸入1~9之間的整數\t"); 52 scanf("%d",&n); // 輸入的時候需要主要n為1~9之間的整數 53 dfs(1); // 首先站在1號小盒子面前 54 printf("\n"); 55 return 0; 56 }
運行結果如下:
核心代碼不超過20行,也就是遞歸的思想 ,模板已給出。關於遞歸思想可以看《程序員數學》第六章,個人感覺寫的很好。下面介紹前面介紹的“坑爹的奧數問題”給出遞歸代碼並與其他算法比較
問題描述:https://www.cnblogs.com/guohaoblog/p/9255706.html
方法一:枚舉法(嵌套多重循環)
1 #include <stdio.h> 2 #include <time.h> 3 /* 4 有9個方格 三個一組構成一個數字,兩個數相加等於第三個數,這9個數從1~9中選取 5 且每個數字只能使用一次使得等式成立 例如 173+286=459 6 請問一種有多少中合理的組合? 7 */ 8 9 int main(int argc, char *argv[]) 10 { 11 int a[10],i,total=0; // a[1]~a[9] 表示這9個數 12 double start,end; 13 start = clock(); 14 for(a[1]=1;a[1]<=9;++a[1]) // 第一個數的百位 15 for(a[2]=1;a[2]<=9;++a[2]) // 第一個數的十位 16 for(a[3]=1;a[3]<=9;++a[3]) // 第一個數的個位 17 for(a[4]=1;a[4]<=9;++a[4]) // 第二個數的百位 18 for(a[5]=1;a[5]<=9;++a[5]) // 第二個數的十位 19 for(a[6]=1;a[6]<=9;++a[6]) // 第二個數的個位 20 for(a[7]=1;a[7]<=9;++a[7]) // 第三個數的百位 21 for(a[8]=1;a[8]<=9;++a[8]) // 第三個數的十位 22 for(a[9]=1;a[9]<=9;++a[9]) // 第三個數的個位 23 { // 接下來判斷每一位上的數互不相等 24 if(a[1]!=a[2] && a[1]!=a[3] && a[1]!=a[4] && a[1]!=a[5] && a[1]!=a[6] && a[1]!=a[7] && a[1]!=a[8] && a[1]!=a[9] 25 && a[2]!=a[3] && a[2]!=a[4] && a[2]!=a[5] && a[2]!=a[6] && a[2]!=a[7] && a[2]!=a[8] && a[2]!=a[9] 26 && a[3]!=a[4] && a[3]!=a[5] && a[3]!=a[6] && a[3]!=a[7] && a[3]!=a[8] && a[3]!=a[9] 27 && a[4]!=a[5] && a[4]!=a[6] && a[4]!=a[7] && a[4]!=a[8] && a[4]!=a[9] 28 && a[5]!=a[6] && a[5]!=a[7] && a[5]!=a[8] && a[5]!=a[9] 29 && a[6]!=a[7] && a[6]!=a[8] && a[6]!=a[9] 30 && a[7]!=a[8] && a[7]!=a[9] && a[8]!=a[9] 31 32 && a[1]*100+a[2]*10+a[3] + a[4]*100+a[5]*10+a[6] == a[7]*100+a[8]*10+a[9] ) 33 { 34 total++; 35 printf("%d%d%d+%d%d%d=%d%d%d\n",a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9]); 36 } 37 } 38 end = clock(); 39 printf("\ntotal=%d\n\n",total/2); // 因為輸出的有重復的 比如 173+286=459 與 286+173=459是一樣的 40 printf("total cost time:%lfs\n",(end-start)/1000); 41 return 0; 42 }
執行結果部分截圖:
方法二:采用標記(還是枚舉法)
1 #include <stdio.h> 2 #include <time.h> 3 4 int main(int argc, char *argv[]) 5 { 6 // 算法改進 用book來標記互不相等的數 7 int a[10],i,total=0,book[10],sum; 8 double start,end; 9 start = clock(); 10 for(a[1]=1;a[1]<=9;++a[1]) // 第一個數的百位 11 for(a[2]=1;a[2]<=9;++a[2]) // 第一個數的十位 12 for(a[3]=1;a[3]<=9;++a[3]) // 第一個數的個位 13 for(a[4]=1;a[4]<=9;++a[4]) // 第二個數的百位 14 for(a[5]=1;a[5]<=9;++a[5]) // 第二個數的十位 15 for(a[6]=1;a[6]<=9;++a[6]) // 第二個數的個位 16 for(a[7]=1;a[7]<=9;++a[7]) // 第三個數的百位 17 for(a[8]=1;a[8]<=9;++a[8]) // 第三個數的十位 18 for(a[9]=1;a[9]<=9;++a[9]) // 第三個數的個位 19 { 20 for(i=1;i<10;++i) // 初始化book數組 21 book[i]=0; 22 for(i=1;i<10;++i) // 如果某個數出現過就標記一下 23 book[a[i]]=1; 24 25 // 統計共出現了多少個不同的數 26 sum = 0; 27 for(i=1;i<10;++i) 28 sum+=book[i]; 29 // 如果正好出現了9個不同的數,並且滿足等式條件,則輸出 30 if(sum == 9 && a[1]*100+a[2]*10+a[3] + a[4]*100+a[5]*10+a[6] == a[7]*100+a[8]*10+a[9]) 31 { 32 total++; 33 printf("%d%d%d+%d%d%d=%d%d%d\n",a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9]); 34 } 35 } 36 end = clock(); 37 printf("\ntotal=%d\n\n",total/2); 38 printf("total cost time:%lfs\n",(end-start)/1000); 39 return 0; 40 }
執行結果(部分截圖):
方法三:遞歸調用
1 #include <stdio.h> 2 #include <time.h> 3 4 int a[10]={0},i,total=0,book[10]={0}; 5 6 void dfs(int step) // step表示現在站在第幾個盒子面前 7 { 8 int i; 9 if(step==10) // 如果站在第10個盒子面前,則表示前9個盒子已經放好撲克牌 10 { 11 if(a[1]*100+a[2]*10+a[3] + a[4]*100+a[5]*10+a[6] == a[7]*100+a[8]*10+a[9]) 12 { 13 total++; 14 printf("%d%d%d+%d%d%d=%d%d%d\n",a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9]); 15 } 16 return; // 返回之前的一步(最近調用的地方) 17 } 18 19 // 此時站在第step個盒子面前,應該放哪張牌呢? 20 // 按照1,2,3,。。。,n的順序一一嘗試 21 for(i=1;i<=9;++i) 22 { 23 // 判斷撲克牌i是否還在手上 24 if(book[i]==0) // book[i]為0表示撲克牌還在手上 25 { 26 // 開始嘗試使用撲克牌i 27 a[step] = i; // 將撲克牌i放入到第step個盒子中 28 book[i] = 1; 29 30 dfs(step+1); 31 book[i]=0; 32 } 33 } 34 return; 35 } 36 37 int main(int argc, char *argv[]) 38 { 39 double start,end; 40 start = clock(); 41 dfs(1); // 首先站在一個盒子面前 42 end = clock(); 43 printf("\ntotal=%d\n\n",total/2); 44 printf("total cost time:%lfs\n",(end-start)/1000); 45 return 0; 46 }
執行結果(部分截圖):
通過比較運行時間,想必大家知道針對統一問題,不同算法效率差別還是挺大的,雖說遞歸的效率不高,但是在針對小規模數據時,表現還是可以的 。對遞歸的深入理解可以多看看大咖博客,自己斷點跟蹤調試。