詳解Python中遞歸函數(shù)的原理與使用
什么是遞歸函數(shù)
如果一個函數(shù),可以自己調(diào)用自己,那么這個函數(shù)就是一個遞歸函數(shù)。
遞歸,遞就是去,歸就是回,遞歸就是一去一回的過程。
遞歸函數(shù)的條件
一般來說,遞歸需要邊界條件,整個遞歸的結(jié)構(gòu)中要有遞歸前進(jìn)段和遞歸返回段。當(dāng)邊界條件不滿足,遞歸前進(jìn),反之遞歸返回。就是說遞歸函數(shù)一定需要有邊界條件來控制遞歸函數(shù)的前進(jìn)和返回。
定義一個簡單的遞歸函數(shù)
# 定義一個函數(shù) def recursion(num): print(num) if num == 0: return 'ok' # 這個函數(shù)在自己的作用域中調(diào)用自己,這個函數(shù)就是一個遞歸函數(shù) recursion(num-1) recursion(10) """ 結(jié)果: 10 9 8 7 6 5 4 3 2 1 0 """
代碼解析
我們執(zhí)行這個函數(shù),參數(shù)給了一個10,但是這個函數(shù)執(zhí)行的過程中,又調(diào)用了自己,所以現(xiàn)在這個函數(shù)就會進(jìn)入先執(zhí)行第二次調(diào)用自己的過程中,第一次的調(diào)用就暫時的阻斷了;
第二次調(diào)用的時候,num-1,參數(shù)就變成了9,繼續(xù)執(zhí)行,然后就又執(zhí)行到了調(diào)用自己的代碼中,現(xiàn)在就會執(zhí)行第三次調(diào)用的過程中,第二次的調(diào)用也阻斷了;
這就是 遞 的過程;
…………
第十一次調(diào)用的時候,num-1,層層的嵌套,參數(shù)就變成了0,就符合了作用域中的if num == 0
的條件判斷式,第十一次的調(diào)用就沒有再執(zhí)行到了調(diào)用自己的代碼,而是徹徹底底的執(zhí)行完成了 ,然后代碼的執(zhí)行就又回到了第十次的函數(shù)調(diào)用中。
第十次的函數(shù)調(diào)用阻斷的時候是執(zhí)行到了recursion(num-1)
,但是這一行代碼執(zhí)行完了,也就是第十一次調(diào)用執(zhí)行完了,并且后面也沒有任何代碼,所以第十次調(diào)用也結(jié)束了,然后就回到了第九次調(diào)用;然后第八次;再就是第七次,一直到第一次結(jié)束,整個函數(shù)的執(zhí)行就結(jié)束了;
這就是 歸 的過程。
內(nèi)存棧區(qū)堆區(qū)
棧區(qū)空間就是我們常說的棧,棧是一個有去有回,先進(jìn)后出后出的空間,就像我們上面的例子中講的,我們最先執(zhí)行的是函數(shù)的第一次調(diào)用,但是第一次調(diào)用卻是最后執(zhí)行釋放掉的,而第十一次調(diào)用是最后調(diào)用,最先執(zhí)行釋放掉的,這就是先進(jìn)后出。與??臻g概念相違背的是隊(duì)列空間,隊(duì)列空間是一個有去有回,先進(jìn)先出的空間,就像我們平時排隊(duì)一樣,先排隊(duì)的會比后來的人先買到票,之后我們學(xué)習(xí)并發(fā)的時候,我們會詳細(xì)的講述隊(duì)列的概念。
單獨(dú)的講棧堆就是一種數(shù)據(jù)結(jié)構(gòu),棧是先進(jìn)后出的一種數(shù)據(jù)結(jié)構(gòu),堆是排序后的一種樹狀數(shù)據(jù)結(jié)構(gòu)。
棧區(qū)堆區(qū)是內(nèi)存空間,棧區(qū)就是按照先進(jìn)后出的數(shù)據(jù)結(jié)構(gòu),無論創(chuàng)建或銷毀都是自動為數(shù)據(jù)分配內(nèi)存,釋放內(nèi)存,這是系統(tǒng)自動創(chuàng)建的;堆區(qū)就是按照排序后的樹狀數(shù)據(jù)結(jié)構(gòu),可優(yōu)先取出必要的數(shù)據(jù),無論創(chuàng)建或者銷毀都是手動分配內(nèi)存,釋放內(nèi)存,這是編程工作者手動編程出來的。
內(nèi)存空間 | 特點(diǎn) |
---|---|
內(nèi)存中的棧區(qū) | 自動分配,自動釋放 |
內(nèi)存中的堆區(qū) | 手動分配,手動釋放 |
運(yùn)行程序時在內(nèi)存中執(zhí)行,會因?yàn)閿?shù)據(jù)類型的不同而在內(nèi)存的不同區(qū)域運(yùn)行,因不同語言對內(nèi)存劃分的機(jī)制不一,當(dāng)大體來說,有如下四大區(qū)域:
- 棧區(qū):分配局部變量空間;
- 堆區(qū):是用于手動分配程序員申請的內(nèi)存空間;
- 靜態(tài)區(qū):又稱之為全局棧區(qū),分配靜態(tài)變量,全局變量空間;
- 代碼區(qū):又稱之為只讀區(qū)、常量區(qū),分配常量和程序代碼空間;
上面的棧區(qū)、讀取、靜態(tài)區(qū)、代碼區(qū)都只是內(nèi)存中的一段空間。
死遞歸
所以我們在使用遞歸函數(shù)的時候要注意,遞歸函數(shù)調(diào)用的過程就是一個開辟棧和釋放棧的過程,調(diào)用的時候開辟棧空間,結(jié)束的時候釋放棧空間,所以說,如果開辟的空間不結(jié)束就會一直存在,就會一直占用內(nèi)存空間,但是??臻g是一個先進(jìn)后出的空間,如果新開啟的空間不釋放掉,之前的空間也不會釋放掉的,那么如果我們開辟的空間很多很多,但是又釋放不掉,那么我們?nèi)跣〉膬?nèi)存是否有足夠的空間容納得下這么多的棧呢?如果容納不下,那么我們的計算機(jī)就會爆炸,所以我們在使用遞歸的時候要注意避免這種情況。尤其是死遞歸。
每次調(diào)用函數(shù)時,在內(nèi)存宗都會單獨(dú)開辟一個空間,配合函數(shù)運(yùn)行,這個空間叫做棧幀空間。
1、遞歸是一去一回的過程,調(diào)用函數(shù)時,會開辟棧幀空間,函數(shù)執(zhí)行結(jié)束之后,會釋放棧幀空間,遞歸實(shí)際上就是不停地開辟和釋放棧幀空間的過程,每次開辟棧幀空間,都是獨(dú)立的一份,其中的資源不共享。
2、觸發(fā)回的過程當(dāng)最后一層棧幀空間全部執(zhí)行結(jié)束的時候,會觸底反彈,回到上一層空間的調(diào)用處,遇到return,會觸底反彈,回到上一層的調(diào)用處
3、寫遞歸時,必須給予遞歸跳出的條件,否則會發(fā)生內(nèi)存溢出,可能會出現(xiàn)死機(jī)的情況,所以當(dāng)遞歸的層數(shù)過多的時候,不建議使用遞歸。
Python中的環(huán)境遞歸的層數(shù)默認(rèn)為1000層左右,一般都是996層。
# 下面的遞歸函數(shù)沒有跳出遞歸的條件,所以是一個死遞歸,執(zhí)行看,是不是1000左右。 def recursion(num): print(num) recursion(num+1) recursion(1)
尾遞歸
簡單的來說,在函數(shù)返回的時候,調(diào)用其本身,并且return語句不包含表達(dá)式,這樣的一個遞歸函數(shù)就是一個尾遞歸函數(shù)。
換句話說返回的東西就是函數(shù)本身就是尾遞歸函數(shù),而遞歸函數(shù)只是自身調(diào)用了自身而已。
一般情況下,尾遞歸的計算在參數(shù)中完成。
我們開始的舉例是一個尾遞歸函數(shù)嗎?不是,因?yàn)槟莻€例子這是調(diào)用了自己本省,但是并沒有返回,所以還是一個普通的遞歸函數(shù)而已。
使用遞歸的時候,我們通常在一些技術(shù)博客上看到一些關(guān)于尾遞歸優(yōu)化的東西,這是因?yàn)槲策f歸無論調(diào)用多少次函數(shù),都只會占用一份空間,只開辟一個棧幀空間,但是目前 cpython 不支持,然而最常見的解釋器就是 cpython 。
Python常見的解釋器:cpython、pypy、jpython。
尾遞歸相比普通遞歸的優(yōu)點(diǎn)就是返回值不需要額外過多的運(yùn)算。
實(shí)例
階乘計算
一個正整數(shù)的階乘是所有小于及等于該數(shù)的正整數(shù)的積,并且0的階乘為1。
""" 循環(huán)計算 """ def factorial(num): if num == 0: return 1 elif num < -1: return '只能是零和正整數(shù)' count = 1 for i in range(1, num+1): count *= i return count res = factorial(5) print(res) # 120 """ 使用普通遞歸 """ def factorial(num): if num == 0: return 1 elif num < -1: return '只能是零和正整數(shù)' elif num == 1: return num return num * factorial(num-1) # 普通函數(shù)返回的是一個表達(dá)式 res = factorial(5) print(res) # 120 """ 使用尾遞歸 """ def factorial(num, count=1): if num == 0: return 1 elif num < -1: return '只能是零和正整數(shù)' elif num == 1: return count return factorial(num-1, count*num) # 尾遞歸返回的是一個函數(shù),計算式在參數(shù)中運(yùn)算 res = factorial(5) print(res) # 120
斐波那契數(shù)列
斐波那契數(shù)列是以0、1兩個數(shù)開頭,從這個數(shù)列從第3個數(shù)開始,每一個數(shù)都等于前兩樹之和。
# 使用循環(huán)解決 def fibonacci(num): x, y = 0, 1 if num < 1: return '輸入大于0的數(shù)字' elif num == 1: return 0 elif num == 2: return 1 for _ in range(num-2): x, y = y, y+x return y res = fibonacci(9) print(res) # 21 """ 使用普通遞歸 """ def fibonacci(num): if num < 1: return '輸入大于0的數(shù)字' elif num == 1: return 0 elif num == 2: return 1 return fibonacci(num-1) + fibonacci(num-2) res = fibonacci(9) print(res) # 21 """ 使用尾遞歸 """ def fibonacci(num, x=0, y=1): if num < 1: return '輸入大于0的數(shù)字' elif num == 1: return x elif num == 2: return y return fibonacci(num-1, x=y, y=(x+y)) res = fibonacci(9) print(res) # 21
到此這篇關(guān)于詳解Python中遞歸函數(shù)的原理與使用的文章就介紹到這了,更多相關(guān)Python遞歸函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python中的split、rsplit、splitlines用法說明
這篇文章主要介紹了python中的split、rsplit、splitlines用法說明,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10

Python實(shí)現(xiàn)隨機(jī)生成一個漢字的方法分享

python中的set實(shí)現(xiàn)不重復(fù)的排序原理

Python?PEP8?代碼規(guī)范常見問題及解決方法

編寫Python爬蟲抓取豆瓣電影TOP100及用戶頭像的方法

Python使用JDAudioCrawler將下載的音頻存儲到本地

selenium+python自動化78-autoit參數(shù)化與批量上傳功能的實(shí)現(xiàn)