如何理解遞歸?

2021-02-19 Java面試那些事兒
前語:不要為了讀文章而讀文章,一定要帶著問題來讀文章,勤思考。

作者:劉半仙  來源:http://1t.click/eWx

# 遞歸是什麼?

定義:程序調用自身的編程技巧稱為遞歸。

它分為調用階段和回退階段,遞歸的回退順序是它調用順序的逆序。

遞歸使用的是選擇結構:if/switch。而for,while,do while使用的是循環結構。

定義不明白不要緊,先思考以下表達式,要怎麼寫程序來計算呢?

1+2+3....+100=?

很多人第一反應使用for循環來解決:

int sum =0;for (int i = 1; i <= 100; i++) {  sum+=i;}System.out.println(sum);

或者二逼青年使用最簡潔而且高效的公式(推薦使用,開銷最小,且一步到位):

  int start =1;  int end = 100;  int sum =(start+end)*end/2;//首項加末項乘以項數除以二  System.out.println(sum);//5050

而遞歸代碼如下:

static  int  recursion(int n){  if(n==1){    return 1;  }else{    return n+recursion(n-1);  }}

通過初體驗對比,不難發現以下遞歸有以下幾個要點。

優點:使程序結構更清晰,更簡潔,更容易讓人理解。

缺點:使用遞歸調用時,如果過多的調用容易造成java.lang.StackOverflowError即棧溢。

出和程序執行過慢。這是一個潛在Bug和影響程序執行效率問題,需要謹慎使用。對於網際網路這種以速度和效率來維護用戶量,不得以用遞歸時,可以把處理的數據放入緩存,或者直接使用迭代等方式來解決。

規律:遞歸要有出口,不然成了死循環。解出遞歸的要點在於求出n-1,求出了n-1才能求解出n,這是為什麼呢?

# 遞歸的執行過程

前文對於遞歸的定義中說,遞歸分為調用階段和回退階段,遞歸的回退順序是它調用順序的逆序。為了理解執行過程,這裡配合實例講解。請思考如何寫出它的遞歸代碼:

n!=n*(n-1)*(n-2)...*3*2*1

這裡給出數學表達式:

            

從表達式中可以明顯的可以看出:

遞歸有出口;

遞歸是選擇結構。

遞歸代碼也不難。

                              

它的調用順序是怎麼樣的呢?

            

當你傳入5時,方法內會去調用方法factorial(4),然後又調用方法factorial(3)直到factorial(0)=1時開始返回,下面為更詳細的圖解。

1、調用階段

         

2、回退階段

        

圖中紅色箭頭為調用階段,綠色箭頭為回退階段。這就是遞歸的要點在於求出n-1,求出了n-1才能求解出n,它思想其實和數學中的歸納本質上是相同的。

# 漢諾塔遊戲講解

遊戲規則:有三根柱子,最左邊的一根柱子從下往上按照大小順序摞著64片圓盤。需要你藉助中間的柱子把左邊的所有圓盤移動到最右邊的柱子上。並且規定,下面圓盤始終要比上面的大,在三根柱子之間一次只能移動一個圓盤。求出最少移動的次數。

如果要把X柱上得3個圓盤移動到Z柱,步驟如下:

 ①把X柱最上面的小圓盤移動到Z柱。②再把X柱中間大的圓盤移動到Y柱。③把Z柱上最小的圓盤移動到Y柱上。

這三次操作如圖所示:

                              

再加上步驟④⑤⑥⑦,所以3個圓盤至少需要移動7次。如果移動6個圓盤時:

                                     

所以,可以推出,當n個從x柱,經由y柱中轉,移動到z柱(解出n層漢諾塔時),有:

當n=0時,不用做任何操作;

當n>0時,

首先,將n-1個盤子從x藉助z移動到y

然後,將1個盤子從x移動到z

最後,將在中間y上的n-1個盤子藉助x移動到z

為了解出n層漢諾塔,需要先使用n-1層漢諾塔的解法。

從推到過程中我們可以發現:解出遞歸的要點在於求出n-1,求出了n-1才能求解出n。此外,從數學角度也可以歸納出 0,1,3,7,15,63...表達式為:f(n)=2^n - 1。

現在,我們可以給出代碼:

static int t=0;public static void main(String[] args) {  hanio(3,"x","y","z");  System.out.println(t);} static void  hanio(int n ,String src,String mid,String dest){  if(n==1){    System.out.println(src+"-->"+dest);    t++;  }else{    hanio(n-1,src,dest,mid);    hanio(1,src,"",dest);    hanio(n-1,mid,src,dest);  }}

# 總結和展望

遞歸對於解決某些問題非常方便(比如遞歸讀取文件路徑),也易於理解。雖然用迭代不是不可以實現,只是同樣為了解決某些特性問題,寫出迭代的代碼花費的時間和難度卻比遞歸高。

前文提到,遞歸和數學中的歸納思想本質上是相同的,都是"將複雜的問題簡化"。掌握遞歸思想的核心就在於"把握結構",因為把握結構是分解整個問題的突破口。

熱文推薦

MyBatis憑神馬能獲取到接口參數名?

原創:如何來判斷一個List是否有序?

相關焦點

  • C語言的「遞歸函數」這麼難理解,為什麼不丟棄它呢?
    初學者在學習C語言的過程中,遇到「遞歸」的概念時,常常會感到迷惑。坦誠地說,「遞歸」在程式語言中的確是一個比較難理解的概念,而且「遞歸」能解決的問題,一般循環語句也能解決,從某種程度上來說,C語言中的「遞歸」和循環語句是等價的,既然如此,為什麼C語言不「丟棄」難以理解的「遞歸」呢?
  • python遞歸算法(上)
    原理很好理解,就是不斷的調用自身,如果前面不加上if條件判斷,理論上是會陷入死循環的,但是實際上遞歸到一定次數(最大遞歸次數)就會報錯停止。遞歸有什麼用知道遞歸是怎麼回事,那麼遞歸有什麼實際用處嘛,或者說有什麼獨特之處。
  • 高斯求和如何用遞歸實現,Python詳解遞歸那些事,看這1篇足夠!
    但是,用我們自己聽得懂的話來講,就是「自己調用自己」的方式;那麼,很容易理解,遞歸函數就是在函數實現過程中,直接或間接調用了函數本身的函數。【舉個例子】我們知道,國外有一個很出名的數學家高斯,在他9歲的時候,用很短的時間計算出了小學老師布置的任務,對自然數從1到100的求和。
  • 計算機編程必備技巧——遞歸使用
    1、引言 今天我們來學習遞歸,如果單說學習算法, 遞歸併不能說是算法,而是一種編程的手法,為什麼現在要學習這個呢?因為後面在學習其他算法時,要牽涉一些遞歸的調用方法,是為以後理解學習的內容做好鋪墊。
  • 計算機編程必備技巧——使用遞歸
    1、引言今天我們來學習遞歸,如果單說學習算法, 遞歸併不能說是算法,而是一種編程的手法,為什麼現在要學習這個呢?因為後面在學習其他算法時,要牽涉一些遞歸的調用方法,是為以後理解學習的內容做好鋪墊。遞歸方法作為一種優雅的解題方法,是大多數程式設計師比較喜歡的編寫方式之一。
  • 詳細解讀Python 遞歸函數!
    今天在 codewar 做題目玩,做到一題時,想試試用遞歸寫,卻發現在函數體內一切正常,可是列印返回值時,列印出來的結果卻是 None 。不明白是在哪裡除了問題了,於是在網上翻了一圈,自己又仔細思考了下整個邏輯。似乎弄清楚了。這裡我來解釋下我的理解。首先,我來寫一個簡化的遞歸函數,來做用於解釋的案例。
  • python算法遞歸於尾遞歸!
    遞歸概念遞歸:程序調用自身的編程技巧稱為遞歸( recursion)。用一種通俗的話來說就是自己調用自己,它通常把一個大型複雜的問題層層轉化為一個與原問題相似的、但是規模較小的問題來求解,當問題小到一定規模的時候,需要一個遞歸出口返回。
  • JAVA開發之——遞歸算法
    一、遞歸的定義和優缺點遞歸算法的定義:遞歸算法是一種直接或者間接地調用自身算法的過程。遞歸算法的優缺點: (1) 在計算機編寫程序中, 遞歸算法對解決一大類問題是十分有效的,它往往使算法的描述簡潔而且易於理解。 (2) 遞歸算法解題通常顯得很簡潔,但運行效率較低。所以一般不提倡用遞 歸算法設計程序。 (3) 在遞歸調用的過程當中系統為每一層的返回點、局部量等開闢了棧來存 儲。遞歸次數過多容易造成棧溢出等。
  • JavaScript函數 - 遞歸
    什麼是函數遞歸?遞歸是指函數自己調用自己。注意:我們可以寫出遞歸,但是我們並不知道她如何得出結果。1.面試官如果問你遞歸的相關知識,面試官的水平還不錯。2.但是在工作中,一般情況下禁止使用遞歸。>計算1加到n的和分析:先來封裝一個函數為sun(n); 找出臨界值,如果n==1時,就直接return返回1然後我們先來假設計算到100的和,就要用sum(100) = sum(99)+100;前99的和再加上100這樣可以得出1加到n的公式,sum(n) = sum(n-1)+n;從sum(1)加到sum(100)這就是一個遞歸
  • Python進階之遞歸函數的用法及其示例
    遞歸是指函數/過程/子程序在運行過程序中直接或間接調用自身而產生的重入現象。在計算機編程裡,遞歸指的是一個過程:函數不斷引用自身,直到引用的對象已知。使用遞歸解決問題,思路清晰,代碼少。(來源於百度,看不懂正常,術語就是不說人話)下面是筆者的個人理解:遞歸就是在函數內部調用自己的函數被稱之為遞歸。看不懂?形象的舉幾個例子!一個洋蔥是一個帶著一層洋蔥皮的洋蔥。
  • 學習編程的人,怎麼能不知道什麼叫遞歸?
    話不多說,開始今天的學習:遞歸:不要看這個名字好像挺高大上的樣子,其實理解起來還是蠻容易的。在學習遞歸之前,我們先學習下目錄的遍歷,遞歸的主要使用途徑就需要它。其中上述兩種方法中:for循環的方法要更加地實用簡潔,使用遞歸的話效率會很低,一般使用的很少。那為何還要學遞歸?因為它在文件操作中會使用到它,並且既然是學習Java,也有必要理解下遞歸的概念。
  • 分型、遞歸、運動原理如何影響股票價格的變動?
    2.遞歸:遞歸可以理解成為隸屬,從屬關係,就是小級別的要服從大級別。操作的準確性就是靠他保證的。打個比喻,在戰場上,一個連隊正在前進,那麼他下屬的班排前進的方向一定是連隊運動的方向。如果把股市的周線方向比作連級運動方向,日線運動方向比作排級運動方向,分級比作班,那麼當周日向上的時候,分級是沒有選擇權利的,只能向上。這也就是為什麼會背了又背。
  • 程式設計師必會算法系列之——「遞歸算法」
    遞歸的缺點使用遞歸算法時每次方法的調用都需要在棧中開闢出一個空間保存相關數據,頻繁的壓棧、彈棧會導致效率變低。遞歸使用注意事項1、使用遞歸算法解決問題必須要有出口,不然就形成死循環了。好好的遞歸變成了「死歸」!2、遞歸的調用次數不宜過多不然會造成棧溢出。總結能通過循環解決的問題儘量不要使用遞歸,循環要比遞歸的效率高很多。
  • 如何優雅地使用javascript遞歸畫一棵結構樹
    本文轉載自【微信公眾號:趣談前端,ID:beautifulFront】經微信公眾號授權轉載,如需轉載與原文作者聯繫遞歸和尾遞歸簡單的說,遞歸就是函數自己調用自己,它作為一種算法在程序設計語言中廣泛應用。
  • 經常被遞歸解題難倒?不如試試「遞歸四步曲」(含實戰演練)
    所以解遞歸題的關鍵在於我們首先需要根據以上遞歸的兩個特點判斷題目是否可以用遞歸來解。接下來讓我們看看用我們之前總結的遞歸解法四步曲如何解題 1.定義一個函數,這個函數代表了翻轉以 root 為根節點的二叉樹public static class TreeNode {
  • 詳解:遞歸神經網絡和LSTM網絡那些事兒
    【IT168 編譯】遞歸神經網絡是最先進的順序數據算法之一,在蘋果Siri和Google語音搜索中都使用到的算法。這是因為它是第一個記憶它的輸入的算法,由於內部存儲器,這使得它非常適合涉及順序數據的機器學習問題。它是過去幾年Deep Learning的驚人成就背後的算法之一。在這篇文章中,你將學習遞歸神經網絡如何工作的基本概念,最大的問題是什麼以及如何解決它們。
  • 知識卡片 遞歸神經網絡
    如何處理否定現象英文中,對於香水的評論更是具有文學或者藝術性的表達,對機器來說理解起來比較複雜。            EMNLP 2014. 1532‐ ‐1543遞歸神經網絡Recusive Neural Networks如何判斷一段文本的情感傾向?
  • 【超詳細】一文學會遞歸解題
    本文試圖從以下幾個方面來講解遞歸力爭讓大家對遞歸的認知能上一個新臺階,特別會對遞歸的精華:時間複雜度作詳細剖析,會給大家總結一套很通用的求解遞歸時間複雜度的套路,相信你看完肯定會有收穫什麼是遞歸簡單地說,就是如果在函數中存在著調用函數本身的情況,這種現象就叫遞歸。
  • C/C++中的遞歸的應用
    (3)遞歸運行過程中,相互嵌套的多層之間會有參數傳遞,多層之間是否會相互影響?二、遞歸兩個要素遞歸的兩個要素分別是遞歸邊界和遞歸的邏輯,即遞歸"公式"。遞歸的過程一定有參數的變化,並且參數的變化和遞歸邊界有關係。在難度較大的程序設計中,這兩者均不易直接得到。下面通過幾個簡單的例子來解釋遞歸。
  • File遞歸【遞歸遍歷目錄】
    File文件基本操作:案例需求:給定一個路徑(E:\Hmw),通過遞歸完成遍歷該目錄下所有內容,並把所有文件的絕對路徑輸出在控制臺