1. 程式人生 > >遞迴呼叫詳解,分析遞迴呼叫的詳細過程

遞迴呼叫詳解,分析遞迴呼叫的詳細過程

一、棧
     在說函式遞迴的時候,順便說一下棧的概念。
     棧是一個後進先出的壓入(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("%d/n",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("%d/n",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<n;i++)
             {
                 if(i<2) array=1;
                 else array=array+array;
             }
             printf("%d/n",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("%d/n",s2);
             getch();
         }

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

相關推薦

呼叫分析呼叫詳細過程

一、棧      在說函式遞迴的時候,順便說一下棧的概念。      棧是一個後進先出的壓入(push)和彈出(pop)式資料結構。在程式執行時,系統每次向棧中壓入一個物件,然後棧指標向下移動一個位置。當系統從棧中彈出一個物件時,最近進棧的物件將被彈出。然後棧指標向上移動一個位置。程式設計師經常利用棧這種資料

通過認識(recursion)

首先先對遞迴進行入門。 遞迴是以自相似的方式重複專案的過程。在程式語言中,如果程式允許您在同一函式內呼叫函式,則稱其為函式的遞迴呼叫。 簡而言之,遞迴就是函式的自身呼叫。可以看看下面的遞迴使用: void Recursive() { Recursive();//call itself

Java中CAS分析的通俗易懂

在JDK 5之前Java語言是靠synchronized關鍵字保證同步的,這會導致有鎖 鎖機制存在以下問題: (1)在多執行緒競爭下,加鎖、釋放鎖會導致比較多的上下文切換和排程延時,引起效能問題。 (2)一個執行緒持有鎖會導致其它所有需要此鎖的執行緒掛起。 (3)如果

【Spring MVC】DispatcherServlet(容器初始化超詳細過程原始碼分析

DispatcherServlet類相關的結構圖DispatcherServlet的初始化程式DispatcherServlet初始化了什麼,可以在其initStrategies()方法中知曉,這個方法如下: protected void initStrategies(App

演算法

1. 何為遞迴? 遞迴在我們的生活中其實很常見。假設你去電影院看電影,黑漆漆一片,你不知道自己來到了第幾排,於是你問前面的人他是第幾排,知道了前面的人是第幾排,加一也就是你所在的排數。但前面的人也不知道,於是他也繼續向前問,直到第一排的人回答他在第一排,然後再依次往後傳,最後

java經典面試題:單鏈表反轉問題(含法)

java經典面試題:單鏈表反轉問題,有兩種方法,一種為迴圈遍歷法,一種遞迴法。 1、迴圈遍歷法   首先設定三個節點,把當前節點的下一節點指向它前面的節點,此時你會發現指標鏈會斷,所以要先把它後面一個節點用nextNode儲存下來,之後把節點向後移動遍歷即可。    程式碼如下: //

LeetCode 856 思路

題目描述 給定一個平衡括號字串 S,按下述規則計算該字串的分數: () 得 1 分。 AB 得 A + B 分,其中 A 和 B 是平衡括號字串。 (A) 得 2 * A 分,其中 A 是平衡括號字串。

入門(十) ---- 列印數字(原理)

wechat:812716131 ------------------------------------------------------ 技術交流群請聯絡上面wechat ----------------------------------------------

HDU - 1995 奇妙的塔 (漢諾塔思想

用1,2,...,n表示n個盤子,稱為1號盤,2號盤,...。號數大盤子就大。經典的漢諾塔問  題經常作為一個遞迴的經典例題存在。可能有人並不知道漢諾塔問題的典故。漢諾塔來源於  印度傳說的一個故事,上帝創造世界時作了三根金剛石柱子,在一根柱子上從下往上按大小 

PYTHON 函式

Python 遞迴函式 詳解 在函式內呼叫當前函式本身的函式就是遞迴函式 下面是一個遞迴函式的例項: 第一次接觸遞迴函式的人,都會被它呼叫本身而搞得暈頭轉向,而且看上面的函式呼叫,得到的結果會是: 為什麼會得出上面的結果呢?因為都把呼叫函式本身之後的程式

全排列演算法

一、概述 全排列在很多程式都有應用,是一個很常見的演算法,常規的演算法是一種遞迴的演算法,這種演算法的得到基於以下的分析思路。 給定一個具有n個元素的集合(n>=1),要求輸出這個集合中元素的所有可能的排列。 二、遞迴實現 2.1、例項一

函式

遞迴函式就是在函式體內部呼叫自己的函式眾所周知,遞迴思維在程式設計界影響深遠,一重遞迴簡單明瞭,很容易看出執行順序,但是遞迴有了巢狀後,你是否還能理解其執行過程呢?1.單重遞迴。#include<iostream>#include<string>usi

定義函式(

函式:就是功能(實現這個功能的過程)  1、定義函式:     返回值  函式名(形參)     {     }     static 返回值 函式名(形參)     {     } 注:如果被修飾為static,此函式只能被本地呼叫。 2、呼叫函式:執行一次函式 注

二叉樹3種遍歷演算法與非實現

一, 二叉樹先序遍歷的實現     遞迴實現 void PreOrderTraverse(BiTree T) { if( T ) { VisitF(T->data);//訪問根節點 PreOrderTra

Clojure的(上)—— 直接及優化

遞迴可以說是LISP的靈魂之一,通過遞迴可以簡潔地描述數學公式、函式呼叫,Clojure是LISP的方言,同樣需要遞迴來扮演重要作用。遞迴的價值在於可以讓你的思維以what的形式思考,而無需考慮how,你寫出來的程式碼就是數學公式,就是函式的描述,一切顯得直觀和透明。如果你

C++ (一)

這幾天看了一下遞迴 1.遞迴 遞迴:一個物件部分地由它自己組成或定義,稱為遞迴。 遞迴函式:一個函式直接或間接地呼叫自身。 遞迴呼叫:在呼叫一個函式的過程中又出現直接或間接地呼叫該函式本身,稱為函式的

Oracle 查詢

建立表和主外來鍵 CREATE TABLE SC_DISTRICT ( ID NUMBER(10) NOT NULL, PARENT_ID NUMBER(10), NAME VARCHAR2(

[轉載]AMOLED結構BOE專家給你分析驅動補償

晶體 即使 長時間 http 如何 電容 環境 抽取 分享 關鍵詞: AMOLED, 驅動補償 有機發光顯示二極管(OLED)作為一種電流型發光器件已越來越多地被應用於高性能顯示中。由於它自發光的特性,與LCD相比,AMOLED具有高對比度、超輕薄

八皇後問題(歸方法

span isp als using ostream 開始 int 對角線 沒有 八皇後遞歸詳解 核心代碼如下: //八皇後遞歸解法 #include<iostream> using namespace std; int queen[9] = {-1,-1

python標準庫常用模組(三)-----------------------------OS模組呼叫系統的命令

OS模組是呼叫系統命令的模組,和執行cmd的命令相似。 1.獲取當前的工作目錄:getcwd() 2.改變當前指令碼的工作目錄:chdir(path) 3.返回當前目錄:curdir() 4.獲取當前目錄的父目錄:pardir() 5.若目錄為空,則刪除,遞迴到上一