1. 程式人生 > >資料結構(C語言版)-遞迴學習筆記

資料結構(C語言版)-遞迴學習筆記

遞迴,介紹瞭解決某一類問題的思維方式。在一個函式定義中出現了對自己本身的呼叫,稱為直接遞迴。一個函式p的定義中包含了對函式q的呼叫,而q的實現過程中又呼叫了p,即函式呼叫形成了一個環狀呼叫鏈,這種方式稱之為間接遞迴。

一個最簡單遞迴程式設計的例項。

例子1 編寫一個遞迴函式,以正整數n為引數,求n的階乘值 n!

顯然當 ,n!是建立在(n-1)!的基礎上的。由於求解(n-1)!的過程與求解n!的過程完全相同,只是具體實參不同,因而在進行程式設計時,不必再仔細考慮(n-1)!的具體實現,只需藉助遞迴機制進行自身呼叫即可。求n!(用Fact(n)表示)的具體實現過:

int Fact(int n)

{

int m;

if (n==0)

{

m = 1;

return m;

}

else

{

m = n*Fact(n-1);

return m;

}

}

由以上例項可以看出,要使用遞迴技術進行程式設計,首先必須將要求解的問題分解成若干子問題,這些子問題的結構與原問題的結構相同,但規模較原問題較小。由於子問題與原問題結構相同,因而他們的求解過程相同,在進行程式設計時,不必再仔細考慮子問題的求解,只需藉助遞迴機制進行函式自身呼叫加以實現,然後利用所得到的子問題的解組合成原問題的解即可;而遞迴程式在執行過程中,通過不斷修改引數進行自身呼叫,將子問題分解成更小的子問題進行求解,直到最終分解成的子問題可以直接求解為止。因此,遞迴程式設計時必須要有一個終止條件,當程式的執行使終止條件得到滿足時,遞迴過程便結束並返回;否則遞迴將會無休止的進行下去,導致程式的執行無法正常終止。

綜上所述,遞迴程式設計具有以下兩個特點:

(1)具備遞迴出口。遞迴出口定義了遞迴的終止條件,當程式的執行使它得到滿足時,遞迴執行過程便終止。有些問題的遞迴程式可能存在幾個遞迴出口。

(2) 在不滿足遞迴出口的條件下,根據所求解問題的性質,將原問題分解成若干個子問題,子問題的求解通過以一定的方式修改引數進行函式自身呼叫加以實現,然後將子問題的解組合成原問題的解遞迴呼叫時,引數的修改最終必須保證遞迴出口得以滿足。

遞迴的執行過程

例子2  編寫一個遞迴函式,以正整數n為引數,該函式所實現的功能是:在第一行輸出一個1,在第2行輸出2個2,在第3行輸出3個3,…,在第n行輸出n個n。例如,當n=5時,該函式的結果為

1

22

333

4444

55555

假設該函式採用print(n)表示。顯然,print(n)所實現的功能只需在print(n-1)的基礎上,再在第n行列印n個n即可;而實現print(n-1)的過程與實現print(n)的過程完全相同,只是引數不同而已,因而可以通過遞迴方式加以實現。在遞迴執行過程中,當n=0時遞迴應該終止。

具體演算法:

void print(int  n)

{

int i;

if (n!=0)

{

print(n-1);

for (i = 0; i < n;++i)

{

cout << n << '\t';

}

cout << endl;

}

}

以下是n=5, print(5)的執行過程。

...

Print(5)的執行由(1)(2)兩部分順序組成,而(1)中print(4)的執行又由(3)(4)兩部分順序組成,(3)中print(3)的執行又由(5)(6)兩部分順序組成…直到(9)(10)。(9)中的print(0)執行時,由於n=0滿足遞迴的終止條件,遞迴將不再繼續下去,於是接下來執行(10),當(10)完成以後,意味著(7)中的print(1)完成了,於是執行(8),(8)的完成意味著(5)中的print(2)結束了,於是執行(6)…直到(2)完成後print(5)的執行才結束,函式呼叫最後的輸出結果由(10)(8)(6)(4)(2)部分的輸出結果順序組成。

這個例子中實現的遞迴函式print(n)沒有返回值,因此執行過程較簡單。對有返回值的遞迴函式是怎麼執行的呢?

例子3. 編寫一個遞迴函式,以一個正整數n為引數,求第n項Fibonacci級數的值。

可知當 時,第n項的Fibonacci級數的值等於第n-1項和第n-2項Fibonacci級數的值相加之和,而第n-1項和第n-2項Fibonacci級數值的求解又分別取決於它們各自的前兩項之和,總之,Fibona(n-1)和Fibona(n-2)的求解過程與Fibona(n)的求解過程相同,只是具體實參不同。利用以上這種性質,我們在進行程式設計時便可以使用遞迴技術。Fibona(n-1)和Fibona(n-2)的求解只需要呼叫函式Fibona 自身加以實現即可。

具體演算法見演算法:

int Fibona(int  n)

{

int m;

if (n==1 ||n==2)

{

return 1;

}

else

{

m = Fibona(n-1) + Fibona(n-2);

return m;

}

}

函式Fibona(5)的執行過程如圖。

...

實線表示呼叫動作,虛線表示返回動作,虛線上所標的值為該次函式呼叫後帶回的返回值。無論是實線還是虛線上均標有一個編號,表示遞迴程式執行過程中,所有動作發生的先後順序;圖中每一方框均表示一次新的呼叫,因而執行過程中它們使用的是不同的儲存空間。

在計算Fibona(5)時,形參n=5,由於n>2,所以執行:

m = Fibona(n-1) + Fibona(n-2);

return m;

這一分支,也即相當於執行

m = Fibona(4) + Fibona(3);

return m;

本次呼叫動作在圖中用標有(1)的實線段表示,其對應的語句執行序列存放於方框S1中。

為了求得方框S1中m的值,必須求出Fibona(4) 和 Fibona(3)的值。程式先求Fibona(4)的值,這是一次新的呼叫,必須重新為它的形參n、區域性變數m和返回的地址分配儲存空間,且此時n=4,這次呼叫仍然滿足n>2,所以執行

m = Fibona(3) + Fibona(2);

return m;

此次呼叫動作體現為圖中標(2)的有向實線段,其對應的語句執行序列存放於方框S2中。同樣為了求得方框S2中m的值,必須求出Fibona(3)和 Fibona(2)的值。於是又執行Fibona(3)… 一直到某次呼叫能有一個確切的返回值,如圖中有向虛線段(5),其返回值為1;接下來考慮方框S3中Fibona(1)的呼叫(體現為有向實線段(6)),此次呼叫直接返回值1(有向虛線段(7))。這時,方框S3中賦值語句m= Fibona(2) + Fibona(1)右端的兩個函式呼叫均已分別返回了值1,於是可以求出m的值為2,然後執行方框S3中的return m,將m=2的值返回給方框S2中的Fibona(3),之後再求方框S2中的Fibona(2)的值,如此反覆進行下去,直至最後求出Fibona(5)的值為5.