1. 程式人生 > >計算機演算法設計與分析——遞迴與分治策略(一)

計算機演算法設計與分析——遞迴與分治策略(一)

遞迴:
直接或者間接地呼叫自身的演算法稱為遞迴。用函式自身給出定義的函式成為遞迴函式。

使用遞迴技術往往使函式的定義和演算法的描述簡潔且易於理解。有些資料結構,如二叉樹等,由於其本身固有的遞迴特性,特別適合用遞迴的形式來描述。另外,還有一些問題,雖然其本身沒有明顯的遞迴結構,但用遞迴技術來求解,可以使得設計出的演算法簡捷易懂且易於分析。

每個遞迴函式都必須有非遞迴定義的初始值,否則,遞迴函式就無法計算。

遞迴演算法的例項:

階乘函式:
n!=1 (n=0)
n!=n(n-1)! (n>0)

遞迴程式碼:

int faction(int n)
{
if(n==0)
return
1; else return n*faction(n-1); }

Fibonacci數列:
無窮數列1,1,2,3,5,8,13,21,34……,稱為Fibonacci數列。

遞迴程式碼:

int fibonacci(int n)
{
if(n<=1)
return 1;
else
return fibonacci(n-1)+fibonacci(n-2);
}

Hanoi塔問題

設a,b,c是三個塔座,在塔座a上有一疊共n個圓盤,這些圓盤自下而上,由大到小的疊放在一起,各圓盤從小到大的編號為1,2,……,n。現要求將塔座a上的這一疊圓盤移動到塔座b上,並仍按照同樣的順序疊放,在移動圓盤時應遵守以下的移動規則:
規則(1) 每次只能移動一個圓盤
規則(2) 任何時刻都不允許將較大的圓盤壓在較小的圓盤之上
規則(3) 在滿足移動規則(1)和(2)的前提下,可將圓盤移動至a,b,c中任何一個塔座上
這個問題有一個簡單的解法,假設塔座a,b,c排成一個三角形,構成一個順時針的迴圈。在移動圓盤的過程中,若是奇數次移動,則將最小的圓盤移動到順時針方向的下一個塔座上,若是偶數次移動,則保持最小的圓盤不動,而在其他兩個塔座之間,將較小的圓盤移動到另一座塔座上。

運用遞迴技術解決這個問題,當n=1時,只要將編號為1的圓盤從塔座a直接移動到b就可以了。當n>1時,需要利用塔座c作為輔助塔座,此時要設法將n-1個較小的圓盤依照移動規則從塔座a移動到c,然後將剩下的最大的圓盤從a移動到b,最後再設法將n-1個較小的圓盤依照移動規則從c移動到b。
由此可見,n個圓盤的移動問題可以分解為兩次n-1個圓盤的移動問題。這又可以遞迴的用上述方法來做,所以Hanoi塔的遞迴演算法如下:

void hanoi(int n,int a,int b,int c)
{
if(n>0)
{
hanoi(n-1,a,c,b);
move(a,b);
hanoi(n-1
,c,b,a); } }

其中,hanoi(n,a,b,c)表示將a上自下而上,由大到小疊放在一起的n個圓盤依照移動規則移動到b上,並且仍然按照同樣的順序疊放。在移動的過程中,以塔座c作為輔助塔座,move(a,b)表示將a上編號為n的圓盤移動到b上。

遞迴演算法總結:

像hanoi這樣的遞迴演算法,在執行時需要多次呼叫自身,實現這種遞迴呼叫的關鍵是為演算法建立遞迴呼叫的工作棧。通常,在一個演算法中呼叫另一個演算法,系統需要在執行被呼叫演算法之前先完成三件事:
(1)將所有實參指標、返回地址等資訊傳遞給被呼叫演算法。
(2)為被呼叫演算法的區域性變數分配儲存區。
(3)將控制轉移到被呼叫演算法的入口。
從被呼叫演算法返回呼叫演算法時,系統也要相應的完成三件事情:
(1)儲存被呼叫演算法的計算結果。
(2)釋放分配給被呼叫演算法的資料區。
(3)依照被呼叫演算法儲存的返回地址將控制轉移到呼叫演算法。

遞迴演算法中,當有多個演算法被呼叫時,按照後呼叫先返回的原則進行。演算法之間的資訊傳遞和控制轉移必須通過棧來實現,即系統將整個程式執行時所需要的資料空間安排在一個棧區中,每呼叫一個演算法,就為這個演算法在棧頂分配一個儲存區,每退出一個演算法,就釋放它在棧頂的儲存區。當前正在執行的演算法的資料一定在棧頂。遞迴演算法的實現類似於多個演算法的巢狀呼叫,只不過呼叫演算法和被呼叫演算法是同一個演算法。

由於遞迴演算法結構清晰,可讀性強,且容易被數學歸納法證明演算法的正確性,因此它為設計演算法、除錯程式帶來了很大的方便,然而,遞迴演算法的執行效率很低,無論是耗費的計算時間還是佔用的儲存空間都比非遞迴演算法要多。若在程式中取消遞迴演算法的呼叫,則其執行時間可以大大的減少。因此,有時希望在遞迴演算法中消除遞迴呼叫,使其變成一個非遞迴演算法,例如,採用一個使用者定義的棧來模擬系統的遞迴呼叫工作棧,從而到達將遞迴演算法改為非遞迴演算法的目的。當然僅僅是機械的模擬還不能達到減少計算時間和減少儲存空間的目的,還要根據具體程式的特點對遞迴呼叫的工作棧進行簡化,儘量的減少工作棧的操作,壓縮棧儲存空間以達到節省計算時間和儲存空間的目的。