小論c語言遞迴與遞推
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
遞迴和遞推都是演算法設計中的難點,演算法又十分相近,很多和我一樣學生誤認為是一回事,非常容易混淆。其實它們之間既有相似點,又有明顯的區別。
遞推一般用迴圈來解決,從已知條件到未知逐漸接近結果;
(1)將複雜運算分解為若干重複的簡單運算
(2)後一步驟建立在前一步驟之上
(3)計算每一步驟的方法相同
(4)從開始向後計算出結果
(5)使用迴圈結構,通過多次迴圈逐漸逼近結果
遞迴一般自己呼叫自己,從未知到已知,把規模大的、較難解決的問題變成規模較小的、易解決的同一問題。規模較小的問題又變成規模更小的問題,並且小到一定程度可以直接得出它的解,從而得到原來問題的解。
(1)每一次遞迴都縮小問題規模,直到問題足夠小
(2)使用選擇分支語句
(3)從後往開始逐步逼近
(4)達到最開始,再把初始值帶入往後逐一求解
下面通過例子逐個介紹。
遞推:
一。 解階乘 n! = 1*2*3*4*....*(n-1)*n.
fun(int n) { long s=1; int i; for(i=1;i<=n;i++) s=s*i; return s; }
二。捕魚問題
A,B,C,D,E五個漁夫夜間合夥捕魚,,第二天清A先醒來,他把魚均分五份,把多餘的一條扔回湖中,便拿了自己的一份回家了,B醒來後,也把魚均分五份,把多餘的一條扔回湖中,便拿了自己的一份回家了,C,D,E也按同樣方法分魚。問5人至少捕到多少條魚?
這也是一個遞推問題,遞推關係式為 F(n+1) = F(n)*5/4+1 (i = 1,2,3,4) F(5)即捕魚的總數
#include <stdio.h>int main(){ int i, n, f[5], flag; flag = 1; n = 1; while (flag == 1) { f[0] = 5*n+1; flag = 0; for (i=1; i<5; i++) { if (f[i-1]%4!=0) { flag=1; break; } f[i] = 5*f[i-1]/4+1; } n++; } printf("%d\n",f[4]); return 0;}
三。平面分割
問題:在平面上畫n條封閉的曲線,各曲線之間兩兩相交於兩點,並且任意三條封閉的曲線都不相交於一點,求這樣的n條曲線將平面分成多少個區域? 輸入:輸入多組測試資料,n=0表示輸入介紹。問題分析:設滿足條件的n條封閉曲線可將平面分成an個區域。則 當n=1時,A1=2. 當n=2時,A2=4 當n=3時,A3=8 設當有n-1條曲線時可將平面分成A(n-1)個區域,此時,加入第n條曲線,因為各曲線之間兩兩相交於兩點,所以,第n條封閉曲線與前n-1條曲線共有2*(n-1)個交點,這些交點將第n條曲線截為2*(n-1)段,而每一段將其所在區域一分為二,所以增加了2*(n-1)個區域。 所以有遞推關係: An = A(n-1) + 2*(n-1) 其中,a1 = 2, a2 =4
#include <stdio.h>int main(){ int i, n, f2, f1; while (1) { scanf("%d",&n); if (n==0) { break; } f1 = 2; for (i=1; i<=n; i++) { f2 = f1+2*(i-1); f1 = f2; } printf("%d\n",f2); } return 0;}
可見遞推也是一種思想,是從已知條件出發,用一種具體的演算法,一步一步接近未知,一般採用迴圈結構。遞推演算法在求解的過程中,每一個間量都是已知,而且沒有重複計算,運算簡潔,但是書寫程式碼和l理解程式碼比較難。
遞迴:
一。計算組合數
問題:計算組合數C(10,3)(組合數:從n個不同元素中,任取m(m≤n)個元素併成一組,叫做從n個不同元素中取出m個元素的一個組合;從n個不同元素中取出m(m≤n)個元素的所有組合的個數,叫做從n個不同元素中取出m個元素的組合數。)
我們可以利用組合恆等式:
若表示在n個物品中選取m個物品,則如存在下述公式: C(n,m)= C(n,n-m)= C(n-1,m-1)+C(n-1,m)。典型的遞迴#include <stdio.h>int Cmn(int m, int n ){ if (m<0 || m<n || n<0) { return 0; } if (m==n) { return 1; } if (n==1) { return m; } return Cmn(m-1,n)+Cmn(m-1,n-1);}int main(){ printf("C(10,3) = %d \n",Cmn(10,3)); return 0;}
二。 解階乘 n! = 1*2*3*4*....*(n-1)*n.
遞迴公式: n! = 1 (n=0,1) n!=n*(n-1)! (n>1)long ff(int n){ long f; if(n<0) printf("n<0,input error"); else if(n==0||n==1) f=1; else f=ff(n-1)*n; return(f);}main(){ int n; long y; printf("\ninput a inteager number:\n"); scanf("%d",&n); y=ff(n); printf("%d!=%ld",n,y);}
三。Hanoi 漢諾塔問題
一塊板上有三根針,A,B,C。A針上套有64個大小不等的圓盤,大的在下,小的在上。要把這64個圓盤從A針移動C針上,每次只能移動一個圓盤,移動可以藉助B針進行。但在任何時候,任何針上的圓盤都必須保持大盤在下,小盤在上。求移動的步驟。本題演算法分析如下,設A上有3個盤子:
1將A上的2個圓盤移到B
2將A上的1個圓盤直接移到C
3將B上的2個圓盤移到C其中第2步可以直接實現
將第1步分解:(藉助C)
1.1 將A上的一個圓盤從A移到C
1.2 將A上的一個圓盤從A移到B
1.3 將C上的一個圓盤從C移到B
將第3步分解:(藉助A)
3.1 將B上的一個圓盤從B移到A
3.2 將B上的一個圓盤從B移到C
3.3 將A上的一個圓盤從A移到C
由此可知,將3個圓盤從A移到C,需要7步,將n個圓盤從A移到C需要2的n次方減1步,要進行第n步,需要先進行第n-1步,可分為3個步驟
1將A上n-1個圓盤藉助C移到B
2將A上剩下的1個圓盤直接移到C
3將B上n-1個圓盤藉助A移到C
[cpp]
view plain
copy
print
?
- <code class="language-cpp">#include <stdio.h>
- void Move(int n,char a,char b)
- {
- printf("%d:%c-->%c\n",n,a,b);
- }
- void Hanoi(int n,char a,char b,char c)
- {
- if(n==1)//將A上的1個圓盤直接移到C,遞迴退出條件
- Move(n,a,c);
- else
- {
- Hanoi(n-1,a,c,b);//將A上n-1個圓盤藉助C移到B
- Move(n,a,c);//將A上剩下的1個圓盤直接移到C
- Hanoi(n-1,b,a,c);//將B上n-1個圓盤藉助A移到C
- }
- }
- int main()
- {
- int n;
- printf("\ninput number:");
- scanf("%d",&n);
- Hanoi(n,'A','B','C');
- return 0;
- }
- /* 列印結果
- input number:3
- 1:A-->C
- 2:A-->B
- 1:C-->B
- 3:A-->C
- 1:B-->A
- 2:B-->C
- 1:A-->C
- */</code>
#include <stdio.h>void Move(int n,char a,char b){ printf("%d:%c-->%c\n",n,a,b);}void Hanoi(int n,char a,char b,char c){ if(n==1)//將A上的1個圓盤直接移到C,遞迴退出條件 Move(n,a,c); else { Hanoi(n-1,a,c,b);//將A上n-1個圓盤藉助C移到B Move(n,a,c);//將A上剩下的1個圓盤直接移到C Hanoi(n-1,b,a,c);//將B上n-1個圓盤藉助A移到C }}int main(){ int n; printf("\ninput number:"); scanf("%d",&n); Hanoi(n,'A','B','C'); return 0;}/* 列印結果 input number:3 1:A-->C 2:A-->B 1:C-->B 3:A-->C 1:B-->A 2:B-->C 1:A-->C*/
遞迴函式的主要優點是可以把演算法寫的比使用非遞迴函式時更清晰更簡潔,而且某些問題,特別是與人工智慧有關的問題,更適宜用遞迴方法。遞迴的另一個優點是,遞迴函式不會受到懷疑,較非遞迴函式而言,某些人更相信遞迴函式。編寫遞迴函式時,必須在函式的某些地方使用if語句,強迫函式在未執行遞迴呼叫前返回。如果不這樣做,在呼叫函式後,它永遠不會返回。在遞迴函式中不使用if語句,是一個很常見的錯誤。在開發過程中廣泛使用printf()和getchar()可以看到執行過程,並且可以在發現錯誤後停止執行。
下面通過一個具體例子比較一下遞迴和遞推。
斐波那契數列:1,1,2,3,5,8,13,21……公式F(n)=F(n-1)+F(n-2),F(1)=F(2)=1;這個公式本身是具有遞迴性的。遞迴解:
long f(int n){ if(n<=2) return 1; else return f(n-1)+f(n-2);}
遞推解:
long f(int n){ long t;f1=1,f2=1; if(n<=2) reutrn 1; for(int i=3;i<=n;i++) { t=f1+f2; f1=f2; f2=t; } }
由此可見,遞迴重在“歸”,你要求的是什麼,就直接求什麼,至於要怎麼去呼叫、怎麼去求,讓函式自己一步一步去反覆呼叫;遞推重在“推”,是按照我們平時做數學的方法來算。程式設計時遞迴比較特殊, 表現 在函式自身呼叫自身,這樣程式設計程式碼緊湊,程式的可讀性好,但效率低,還可能導致堆疊溢位,特別是遞迴層次比較多時。一般來說,遞迴程式都可以用迴圈程式實現,用迴圈程式實現雖然較難理解,但 安全 可靠。
從軟體工程的角度來說,遞迴使用程式更加簡單清晰 從效率角度來說,遞推會比遞迴效率高很多 一般選擇遞推代替遞迴,以適當的增加程式複雜性的代價來換取效率