1. 程式人生 > >《演算法圖解》 | 深入理解棧與遞迴

《演算法圖解》 | 深入理解棧與遞迴

1.如何理解遞迴?

遞迴是一種全新的思維方式,初學遞迴總會有知其然卻不知其所以然的感覺,這裡我們可以從迴圈入手,探討遞迴與迴圈的內在差別。

理論上:所有的遞迴都可以用迴圈實現,但實際上有的演算法因為迴圈次數過多,很難轉換。(如:漢諾塔問題)。

我們先來假設這樣一個場景

有一個大盒子,開啟,裡面還是一個盒子,再開啟,還是盒子,直到你開啟很多次後,最後一個小盒子裡放了顆鑽石。

1.如果用迴圈實現 因為你開啟的次數過多,所以你並不能清晰的記住自己打開了幾個盒子,因此這裡無法使用for迴圈,但是可以使用while迴圈,過程大致為:流程 建立要查詢的盒子堆->只要盒子堆不空->開啟一個盒子 ->如果還是盒子(返回步驟2) ->如果是鑽石(萬事大吉)虛擬碼

def look_for_diamond(big_box):
    pile = big_box.make_a_pile_to_look()
    while pile is not empty:
    box = pile.grab_a_box()
    for temp in box:
        if temp.is_a_box():
            pile.look_for_again(temp)
        if temp.is_diamond():
            print "get the diamond!"

2.如果用遞迴實現流程 檢查盒子 ->如果是盒子(返回步驟1) ->如果是鑽石(大功告成)虛擬碼

def look_for_diamond(box):
    for temp in box:
        if temp.is_a_box():
            look_for_diamond(temp)
        elif temp.is_a_key():
            print "get the diamond!"

相對於迴圈來說,遞迴的步驟變少且邏輯更加清晰,但效能上沒有什麼優勢,到底使用遞迴還是迴圈要取決於具體的問題,一般來講迴圈效能可能更高,而遞迴更清晰明瞭。

2.基線條件和遞迴條件

初用遞迴,往往會不慎造成死迴圈,從而導致函數出錯,因此編寫遞迴函式時,必須告訴它何時停止遞迴,因此,每個遞迴函式都有兩部分:基線條件

(base case)和遞迴條件(recursive case)。基線條件指函式不在呼叫自己,而遞迴條件指函式繼續呼叫自己。

3.呼叫棧

向一個玻璃杯裡放乒乓球,放入1號再放入2號再放入3號,然後取出的順序則為3,2,1,這種先進後出的資料結構即為棧。那遞迴與棧又又什麼關係呢?其實遞迴函式就是在呼叫棧! 如(階乘運算):

def fact(x) :
    if x == 1:
        return 1
    else:
        return x*fact(x-1)

當我們輸入3的時候,即執行3*fact(3-1) 此時繼續執行fact(3-1) == fact(2) 則執行2*fact(2-1) 此時繼續執行fact(2-1)==fact(1)==1,返回1 從而得到 1*2*3 = 3! 其實這裡的3*fact(3-1)就是1號乒乓球,2*fact(2-1)就是2號乒乓球,1就是3號乒乓球。 這便是一個重要的概念:當一個函式裡面要呼叫另一個函式時,當前函式暫停並處於未完成狀態,即此時函式的所有變數的值都還在記憶體中,只有當執行完了呼叫的函式時,才會回到當前函式,並繼續向下執行,這個過程就呼叫了棧!

切記:在一個函式中呼叫另一個函式,呼叫的函式是無法訪問其他函式中的變數的,就好比2號乒乓球不能訪問1號和3號乒乓球裡面的東西一樣。

4.總結

棧是計算機自身在呼叫函式時自己開闢出來的,因此不需要自己去實現,使用棧雖然很方便,但是也不可避免的要佔用一定的記憶體,如果棧很高,就意味著計算機為此儲存了大量函式呼叫的資訊,如果遞迴深度過高,甚至會造成計算機宕機,在這種情況下,你有兩種處理方法: 1).轉換為迴圈 2).使用尾遞迴。尾遞迴是一種高階的方法,我們在後續討論。

作者:Unified_line 來源:CSDN 原文:https://blog.csdn.net/qq_38497820/article/details/80954047?utm_source=copy