260 likes | 345 Views
Chapter 3. 遞迴. 3.1 何謂遞迴. 遞迴程式簡單的說即是「一程式呼叫自己本身( call itself)」, 在高階語言中, C、Pascal、Ada 及人工智慧語言 Lisp、Prolog 都提供了遞迴呼叫的功能,根據呼叫形式的不同,又分為: *. 3.1 何謂遞迴. 直接遞迴 若一副程式 P 直接呼叫自己本身,便稱之為直接遞迴。 間接遞迴 若一副程式 P 呼叫另一副程式 Q, 而副程式 Q 又呼叫副程式 P, 此種形式的遞迴關係便稱之為間接遞迴。 *. 3.1 何謂遞迴.
E N D
Chapter 3 遞迴
3.1 何謂遞迴 • 遞迴程式簡單的說即是「一程式呼叫自己本身(call itself)」,在高階語言中,C、Pascal、Ada及人工智慧語言Lisp、Prolog都提供了遞迴呼叫的功能,根據呼叫形式的不同,又分為: *
3.1 何謂遞迴 • 直接遞迴若一副程式P直接呼叫自己本身,便稱之為直接遞迴。 • 間接遞迴若一副程式P呼叫另一副程式Q,而副程式Q又呼叫副程式P,此種形式的遞迴關係便稱之為間接遞迴。 *
3.1 何謂遞迴 • 在一般遞迴呼叫中,為滿足演算法的有限性(definite),所以必須有一結束遞迴呼叫的條件,此即「終止條件」(termination condition)。 *
3.2 一些遞迴的例子 費式數列(Fibonacci number) • 圖3-2-1中,將兩塊玻璃背對放著,設 *
3.2 一些遞迴的例子 • 當n=0時,表示光線方向不變,所以a0=1,而n>1時,可分成兩種情況 • 光線跳到對面層,再以an-1的方式反彈,如a3中的第3、4、5種方式。 • 光線跳到中間層,再以an-2的方式反彈,如a3中的第1、2種方式。 • 由上述說明,可得到一數學關係式 *
3.2 一些遞迴的例子 • 下面我們定義一數列Fn,其下一項值為前二項值之和,且初始條件為F0=0,F1=1 • 很顯然地,此數列滿足 • 這就是「費式數列」的數學定義式。 *
3.2 一些遞迴的例子 • 底下程式則是說明如何求第n項費式數列值的遞迴程式。 // 費氏級數,此項等於前兩項之和int recursion::fib(int n){ if (n<2) return n; else return (fib(n-1)+fib(n-2)); // 遞迴呼叫自已} *
3.2 一些遞迴的例子 • 從費式數列的定義中,可知欲求fib(6)時,必先呼叫fib(5)及fib(4),而在求fib(5)時,又要呼叫fib(4)及fib(3),所有呼叫次序如圖3-2-2。
3.2 一些遞迴的例子 漢諾塔(Hanoi Tower) • 一則有趣的故事傳說:在開天闢地之初,上帝將64個由大而小疊好的金盤放在一根金針上,命令一群教徒利用另兩根金針,把這些金盤搬到第三根金針上,且搬運過程中,必須遵守二條原則 (1)一次只能搬動一個盤子。 (2)小金盤必須在大金盤之上。 • 當他們完成這項工作的時候,也就是世界末日的來到。 *
3.2 一些遞迴的例子 • 想要解決這個複雜的問題,可先考慮兩個盤子的解答。 * 圖3-2-3 兩個金盤的漢諾塔
3.2 一些遞迴的例子 • 聰明的讀者可以發現,如果是搬n個盤子時,亦是同上圖,將n-1個盤子先移到B,再將第n個盤子移到C,最後將n-1個盤子移到C即可完成。 *
3.2 一些遞迴的例子 • 以下程式實作漢諾塔的遞迴程式。 // 漢諾塔void recursion::hanoi(int plates,char source,char target, char put){ if (plates>0) { // 將n-1個盤子先移到B hanoi(plates-1,source,put,target);
3.2 一些遞迴的例子 // 將第n個盤子移到C cout << "Move plate No." << plates << " from "<< source << " to " << target << endl; // 將n-1個盤子移到C hanoi(plates-1,put,target,source); }} *
3.3 解遞迴式 • 在上面的費式數列中,若想知道fib(n)的值究竟有多大,必須呼叫圖3-2-2各節點,所以一時無法推估它的大小,這是許多遞迴程式共同的特徵,為克服此項缺點,我們以費式數列為例子,引進解遞迴式的方法。 *
3.3 解遞迴式 • 考慮遞迴式如下:Fn=Fn-1+Fn-2 ,n>1因為n=1時,左式等於1,右式等於0,所以 • 步驟一 將上式修正成Fn=Fn-1+Fn-2+(n=1) • 步驟二 令G(Z)= FnZn所以G(Z)= FnZn= [Fn-1+ Fn-2 +(n=1)] Z n= FnZn + Fn-2Zn + (n=1)Z n= FnZn+1+ FnZn+2+Z= ZG(Z)+ Z2 G(Z)+Z *
3.3 解遞迴式 • 步驟三 設 • 因此 1-Z-Z2=1-(α+β)Z+αβZ2。 • 所以可得其中一組解 *
3.3 解遞迴式 • (1+√5)/2就是藝術上所稱的「黃金比(golden ratio)」,意含此種比例設計最令人愉快,最後可得 G(Z)=1/√5((1/(1-αZ))-(1/(1-βZ))) =1/√5Σ(αn-βn)Zn 所以 Fn=1/√5(αn-βn) • 因此,若想知道fib(n)之值,即可把n代入上式,即可求出。 *
3.4 使用遞迴的優缺點 • 從圖3-2-2中看出,在計算fib(6)時,遞迴過程中呼叫次數如表3-4-1。 *
3.4 使用遞迴的優缺點 • 因此其效率極差,若我們把它改成非遞迴的程式,效率將可大大改進: // 費氏級數,此項等於前兩項之和int unrecursion::fib(int n){ int i,prev=0,now=1,next; if (n<2) return n; else {
3.4 使用遞迴的優缺點 for (i=2;i<=n;i++) { next=prev+now; prev=now; now=next; } } return now;} *
3.4 使用遞迴的優缺點 • 再看看漢諾塔的效率問題,設Tn表示將n個盤子由一金針搬至另一金針所需的時間,由前面說明可看出,搬移m個盤子共需2Tn-1+1次移動,所以可得 *
3.4 使用遞迴的優缺點 • 我們發現它的時間複雜度(time complexity)呈指數成長,因此即使要搬完64個金盤,也需264次才能完成,縱然每一微秒搬動一次,也要50000世紀才能完成。 *
3.4 使用遞迴的優缺點 • 一般而言,使用遞迴會付出下列兩點代價: 較花時間,效率較差原因有二,第一,編譯器可能將遞迴程式編譯成效差的碼,在最差的情況下,一個遞迴性的程序效率只大約只有非遞迴性程序的一半。第二,我們的遞迴程式也許簡單但效率差,如費式級數就需要重複呼叫副程式多次。 *
3.4 使用遞迴的優缺點 需較多記憶空間因為每一次遞迴呼叫中,都要將原來的變數、參數等存起來,所以遞迴深度愈大所需的記憶體就愈大。 *
3.4 使用遞迴的優缺點 • 但遞迴程式,也具有下列二項優點: 效率分析簡單如上面漢諾塔的分析、簡易清晰。 設計自然、簡單下一節中,讀者可輕易觀察出對應遞迴程式的非遞迴程式,較遞迴程式複雜多了。 *