Python 遞歸函數


一直以為遞歸是一件很簡單的事情,把循環給增加一個對需要遞歸過程的引用就OK了,但到了實際應用的時候發現遠遠不是這樣。

參考鏈接:https://www.liaoxuefeng.com/wiki/897692888725344/897693398334720

主要學到了怎樣讓遞歸以更高效的方式去運行。

作者先講了遞歸的一般寫法,就是對自身的調用。 

def fact(n):
    if n==1:
        return 1
    return n * fact(n - 1)

  隨后作者耐心的給出了當n=5時函數的運行過程

 
        
===> fact(5) ===> 5 * fact(4) ===> 5 * (4 * fact(3)) ===> 5 * (4 * (3 * fact(2))) ===> 5 * (4 * (3 * (2 * fact(1))))#注意程序到這里會運行到最底部,然后一步一步向上走 ===> 5 * (4 * (3 * (2 * 1))) ===> 5 * (4 * (3 * 2)) ===> 5 * (4 * 6) ===> 5 * 24 ===> 120

  不過,這些寫起來雖然邏輯清晰,但是如果遞歸的層數過高就會發生棧溢出的情況,棧溢出是遞歸進行到最底層的過程中,一次一次對函數的調用。在計算機中,函數調用時通過棧這種數據結構來實現的,每當進行一個函數調用,棧就會增加一個棧幀,每當函數返回,就相應的減少一個棧幀。由於棧的大小不是無限的,所以當函數調用的次數過多,就會發生棧溢出。

  解決這種棧溢出的方法就是通過尾遞歸優化,尾遞歸優化的效果和循環的效果是一樣的.

  尾遞歸是指在函數返回的時候調用函數本身,並且return語句不能包含表達式。

  上面的fact(n)函數由於return n * fact(n - 1)引入了乘法表達式,所以就不是尾遞歸了。要改成尾遞歸方式,需要多一點代碼,主要是要把每一步的乘積傳入到遞歸函數中:

  對前面函數改進后的結果如下:

ef fact(n):
    return fact_iter(n, 1)

def fact_iter(num, product):
    if num == 1:
        return product
    return fact_iter(num - 1, num * product)
#可以看到,return fact_iter(num - 1, num * product)僅返回遞歸函數本身,num - 1num * product在函數調用前就會被計算,不影響函數調用。
#可能會有疑惑,覺得把product這個變量放到fact中然后再改一改邏輯就好了,但是不可能的,因為上面的邏輯總共有三個返回,
#不過也是可以的
def fact(n, product):
if n ==1:
return product
product = n*product
return fact(n -1, product)
print(fact(5, 1))
  

  當n=5時對應的fact_iter(5,1)運行歷程如下:

===> fact_iter(5, 1)
===> fact_iter(4, 5)
===> fact_iter(3, 20)
===> fact_iter(2, 60)
===> fact_iter(1, 120)
===> 120

  尾遞歸調用時,如果做了優化,棧的長度不會增長,因此,做多少次函數調用也不會導致棧溢出,遺憾的是,大多數編程語言都沒有對尾遞歸做相應的優化。

使用遞歸函數的優點是邏輯簡單清晰,缺點是過深的調用會導致棧溢出。

針對尾遞歸優化的語言可以通過尾遞歸防止棧溢出。尾遞歸事實上和循環是等價的,沒有循環語句的編程語言只能通過尾遞歸實現循環。

 

Python標准的解釋器沒有針對尾遞歸做優化,任何遞歸函數都存在棧溢出的問題。

上面的fact(n)函數由於return n * fact(n - 1)引入了乘法表達式,所以就不是尾遞歸了。要改成尾遞歸方式,需要多一點代碼,主要是要把每一步的乘積傳入到遞歸函數中:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM