C語言函式遞迴教程

引導語:遞迴做為一種演算法在程式設計語言中廣泛應用。以下是本站小編分享給大家的C語言函式遞迴教程,歡迎閱讀!

C語言函式遞迴教程

一、棧

在說函式遞迴的時候,順便說一下棧的概念。

棧是一個後進先出的壓入(push)和彈出(pop)式資料結構。在程式執行時,系統每次向棧中壓入一個物件,然後棧指標向下移動一個位置。當系統從棧中彈出一個物件時,最近進棧的物件將被彈出。然後棧指標向上移動一個位置。程式設計師經常利用棧這種資料結構來處理那些最適合用後進先出邏輯來描述的程式設計問題。這裡討論的程式中的棧在每個程式中都是存在的,它不需要程式設計師編寫程式碼去維護,而是由執行是系統自動處理。所謂的系統自動維護,實際上就是編譯器所產生的程式程式碼。儘管在原始碼中看不到它們,但程式設計師應該對此有所瞭解。

再來看看程式中的棧是如何工作的。當一個函式(呼叫者)呼叫另一個函式(被呼叫者)時,執行時系統將把呼叫者的所有實參和返回地址壓入到棧中,棧指標將移到合適的位置來容納這些資料。最後進棧的是呼叫者的返回地址。當被呼叫者開始執行時,系統把被呼叫者的自變數壓入到棧中,並把棧指標再向下移,以保證有足夠的空間儲存被呼叫者宣告的所有自變數。當呼叫者把實參壓入棧後,被呼叫者就在棧中以自變數的形式建立了形參。被呼叫者內部的其他自變數也是存放在棧中的。由於這些進棧操作,棧指標已經移動所有這些區域性變數之下。但是被呼叫者記錄了它剛開始執行時的初始棧指標,以他為參考,用正或負的偏移值來訪問棧中的.變數。當被呼叫者準備返回時,系統彈出棧中所有的自變數,這時棧指標移動了被呼叫者剛開始執行時的位置。接著被呼叫者返回,系統從棧中彈出返回地址,呼叫者就可以繼續執行了。當呼叫者繼續執行時,系統還將從棧中彈出呼叫者的實參,於是棧指標回到了呼叫發生前的位置。

可能剛開始學的人看不太懂上面的講解,棧涉及到指標問題,具體可以看看一些資料結構的書。要想學好程式語言,資料結構是一定要學的。

二、遞迴

遞迴,是函式實現的一個很重要的環節,很多程式中都或多或少的使用了遞迴函式。遞迴的意思就是函式自己呼叫自己本身,或者在自己函式呼叫的下級函式中呼叫自己。

遞迴之所以能實現,是因為函式的每個執行過程都在棧中有自己的形參和區域性變數的拷貝,這些拷貝和函式的其他執行過程毫不相干。這種機制是當代大多數程式設計語言實現子程式結構的基礎,是使得遞迴成為可能。假定某個呼叫函式呼叫了一個被呼叫函式,再假定被呼叫函式又反過來呼叫了呼叫函式。這第二個呼叫就被稱為呼叫函式的遞迴,因為它發生在呼叫函式的當前執行過程執行完畢之前。而且,因為這個原先的呼叫函式、現在的被呼叫函式在棧中較低的位置有它獨立的一組引數和自變數,原先的引數和變數將不受影響,所以遞迴能正常工作。程式遍歷執行這些函式的過程就被稱為遞迴下降。

程式設計師需保證遞迴函式不會隨意改變靜態變數和全域性變數的值,以避免在遞迴下降過程中的上層函數出錯。程式設計師還必須確保有一個終止條件來結束遞迴下降過程,並且返回到頂層。

例如這樣的程式就是遞迴:

void a(int);

main()

{

int num=5;

a(num);

}

void a(int num)

{

if(num==0) return;

printf(%d,num);

a(--num);

}

在函式a()裡面又呼叫了自己,也就是自己呼叫本身,這樣就是遞迴。那麼有些人可能要想,這不是死迴圈嗎?所以在遞迴函式中,一定要有return語句,沒有return語句的遞迴函式是死迴圈。

我們分析上面的例子,先呼叫a(5),然後輸出5,再在函式中呼叫本身a(4),接著回到函式起點,輸出4,……,一直到呼叫a(0),這時發現已經滿足if條件,不在呼叫而是返回了,所以這個遞迴一共進行了5次。如果沒有這個return,肯定是死迴圈的。

雖然遞迴不難理解,但是很多在在使用遞迴函式的時候,問題多多。這裡面一般有兩個原因:一是如何往下遞迴,也就是不知道怎麼取一個變數遞迴下去;二是不知道怎麼終止遞迴,經常弄個死迴圈出來。

下面看幾個例子:

1.求1+2+……+100的和

先分析一下。第一遞迴變數的問題,從題目上看應該取1,2,……,100這些變數的值作為遞迴的條件;第二就是如何終止的問題,從題目上看應該是當數為100的時候就不能往下加了。那麼我們試著寫一下程式。

int add(int);

main()

{

int num=1,sn;

sn=add(num);

printf(%dn,sn);

getch();

}

int add(int num)

{

static int sn;

sn+=num;

if(num==100) return sn;

add(++num);

}

分析一下程式:前呼叫add(1),然後在子函式中把這個1加到sn上面。接著呼叫add(2),再把sn加2上來。這樣一直到100,到了100的時候,先加上來,然後發現滿足了if條件,這時返回sn的值,也就是1+2+……+100的值了。

這裡有一個問題一定要注意,就是static int sn;

有些人就不明白,為什麼要使用static型別修飾符,為什麼不使用int sn=0;?如果使用int sn=0;這樣的語句,在每次呼叫函式add()的時候,sn的值都是賦值為0,也就是第一步雖然加了1上來,可是第二次呼叫的時候,sn又回到了0。我們前面說了,static能保證本次初始化的值是上次執行後的值,這樣也就保證了前面想加的結果不會丟失。如果你修改為int sn=0,最後結果一定是最後的100這個值而不是5050。

2.求數列s(n)=s(n-1)+s(n-2)的第n項。其中s(1)=s(2)=1。

可以看出,終止條件一定是s(1)=s(2)=1。遞迴下降的引數一定是n。

int a(int);

main()

{

int n,s;

scanf(%d,&n);

s=a(n);

printf(%dn,s);

getch();

}

int a(int n)

{

if(n<3) return 1;

return a(n-1)+a(n-2);

}

這個題目主要說明的是,在函式中,不一定只有一個return語句,可以有很多,但是每次對歸的時候只有一個起作用。題目不難理解,這兒不分析了。

說了這些遞迴,其實它和函式的呼叫沒有大的區別,主要就是一個終止條件要選好。遞迴函式很多時候都能用迴圈來處理。

main()

{

int n=20,array[20];

int i;

for(i=0;i {

if(i<2) array[i]=1;

else array[i]=array[i-1]+array[i-2];

}

printf(%dn,array[19]);

getch();

}

上面的程式就是實現一模一樣的功能的。但是它有一個缺陷,就是n的值不是通過鍵盤輸入來得到。如果想通過鍵盤來得到n,可以這樣:

main()

{

int n,i;

int s1=1,s2=1,temp

scanf(%d,&n);

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

{

temp=s2;

s2+=s1;

s1=temp;

}

printf(%dn,s2);

getch();

}

但是在某些場合,使用遞迴比使用迴圈要簡單的多。而且有些題目,一看就知道應該使用遞迴而不是迴圈來處理。