詳細解讀Python 遞歸函數!

2020-12-16 愛Python編程的南風

遞歸函數

在函數內部,可以調用其他函數。如果一個函數在內部調用自身本身,這個函數就是遞歸函數。

遞歸函數特性:

必須有一個明確的結束條件;每次進入更深一層遞歸時,問題規模相比上次遞歸都應有所減少相鄰兩次重複之間有緊密的聯繫,前一次要為後一次做準備(通常前一次的輸出就作為後一次的輸入)。遞歸效率不高,遞歸層次過多會導致棧溢出(在計算機中,函數調用是通過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀。由於棧的大小不是無限的,所以,遞歸調用的次數過多,會導致棧溢出)先舉個簡單的例子:計算1到100之間相加之和;通過循環和遞歸兩種方式實現

#!/usr/bin/env python3# 1-100 sumimport sysdef sum_cycle(n): ''' 1 to n,The sum function ''' sum = 0 for i in range(1,n + 1): sum += i return sumdef sum_recu(n): ''' 1 to n,The sum function ''' if n > 0: return n + sum_recu(n - 1)    #調用函數自身 else: return 0print("循環求和:",sum_cycle(100))print("遞歸求和:",sum_recu(100))執行結果:[root@localhost tmp]# ./sum.py循環求和: 5050遞歸求和: 5050遞歸函數的優點是定義簡單,邏輯清晰。理論上,所有的遞歸函數都可以寫成循環的方式,但循環的邏輯不如遞歸清晰。

***使用遞歸函數需要注意防止棧溢出。在計算機中,函數調用是通過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀。由於棧的大小不是無限的,所以,遞歸調用的次數過多,會導致棧溢出。

把上面的遞歸求和函數的參數改成10000就導致棧溢出!

[root@localhost tmp]# ./sum.py循環求和: 5050Traceback (most recent call last): File "./sum.py", line 23, in <module> print("遞歸求和:",sum_recu(1000)) File "./sum.py", line 19, in sum_recu return n + sum_recu(n - 1) File "./sum.py", line 19, in sum_recu return n + sum_recu(n - 1) File "./sum.py", line 19, in sum_recu return n + sum_recu(n - 1) [Previous line repeated 994 more times] File "./sum.py", line 18, in sum_recu if n > 0:RecursionError: maximum recursion depth exceeded in comparison***解決遞歸調用棧溢出的方法是通過尾遞歸優化,事實上尾遞歸和循環的效果是一樣的,所以,把循環看成是一種特殊的尾遞歸函數也是可以的。

尾遞歸優化:http://www.open-open.com/lib/view/open1480494663229.html

二分法查找大家應該聽說過;就是一種快速查找的方法,時間複雜度低,邏輯簡單易懂,總的來說就是不斷的找出中間值,用中間值對比你需要找的實際值;若中間值大,則繼續找左邊;若中間值小,則繼續找右邊;可以看出二分法就是不斷重複此上過程,所以就可以通過遞歸方式來實現二分法查找了!

今天在 codewar 做題目玩,做到一題時,想試試用遞歸寫,卻發現在函數體內一切正常,可是列印返回值時,列印出來的結果卻是 None 。不明白是在哪裡除了問題了,於是在網上翻了一圈,自己又仔細思考了下整個邏輯。似乎弄清楚了。這裡我來解釋下我的理解。

首先,我來寫一個簡化的遞歸函數,來做用於解釋的案例。

def ex(x): if x > 1: x -= 1 ex(x) else: return xt = ex(5)print(t)上面的程序邏輯超簡單:判斷輸入的書是否大於 1 ,如果大於 1 則把它減 1 ,然後再判斷。如此不斷遞歸下去。當 x 不大於 1 的時候,返回 x 的大小。

很容易認為返回的 x 列印出來的結果是 1 是不?但是不是的。你可以把上面的代碼運行下,列印出來的結果是:

None為什麼是這樣的呢?我的理解是:

當我們在 t = ex(5) 的時候,調用了一次函數。如果函數直接返回 t = 1,那是木有問題的。問題是,程序在這裡返回數據。反而又去調用了一次函數本身。這次調用的的是 ex(4) 然後一直調用的 ex(1) 這時候程序終於運行到了 return 這裡。那這次 return 給了誰呢?答案是誰都沒有給。因為我們沒有設置 t = ex(1), 所以函數的 return 回的數據就這麼丟失了。

而我們調用的 t = ex(5) 呢,因為它的 if > 1 是成立的,所以直接跳過了了 else: return x ,等於這一次調用,是沒有用到 return 的。而在 Python 中規定,如果一個函數體沒有 return 的時候,那它的 return 就是 None 。這也就是為什麼,我們運行上面的程序的時候,得到的結果是 None 。

為了證明這點,我們可以去寫一個程序,去嘗試獲得迭代到 ex(1) 這次調用時 ex(1) 所返回的值。程序如下:

t = {}def ex(x): global t if x > 1: x -= 1 t[x] = ex(x) else: return xex(10)print(t)在程序中,我們用字典,把 ex(x) 每次調用所返回的數據記錄了下來。結果如下:

{1: 1, 2: None, 3: None, 4: None, 5: None, 6: None, 7: None, 8: None, 9: None}可見:

在迭代中,後調用的函數先返回。除了最後一次調用的函數有了正確的返回外,之前的函數都沒有 return ,所以返回的都是 None。那如何正確地返回迭代出來的數據呢?答案是:一層一層第把最後一次調用返回出來。代碼如下:

def ex(x): if x > 1: x -= 1 return ex(x) else: return xt = ex(5)print(t)結果為:

1為什麼這次能正確呢?因為第一次調用 ex(x) 的時候,它返回的是ex(x-1),下一次調用返回了 ex(x-2) 一路攜手交傳,最終把 ex(1) return 給了 t ,這個時候就等於 t = ex(1) 了,自然就得到了正確的結果了。

嗯,本文就到這裡要結束了。最後說一下, Python 的確不喜歡迭代。所以它嚴格限制了迭代的次數。我電腦上,只要迭代的次數超過 999 ,就會報錯,如下:

t = ex(998)

1t = ex(999)

Traceback (most recent call last):File "E:\Work\Coder\Test\test4.py", line 14, in <module>t = ex(999)RecursionError: maximum recursion depth exceeded in comparison***解決遞歸調用棧溢出的方法是通過尾遞歸優化,事實上尾遞歸和循環的效果是一樣的,所以,把循環看成是一種特殊的尾遞歸函數也是可以的。

尾遞歸優化:http://www.open-open.com/lib/view/open1480494663229.html

二分法查找大家應該聽說過;就是一種快速查找的方法,時間複雜度低,邏輯簡單易懂,總的來說就是不斷的找出中間值,用中間值對比你需要找的實際值;若中間值大,則繼續找左邊;若中間值小,則繼續找右邊;可以看出二分法就是不斷重複此上過程,所以就可以通過遞歸方式來實現二分法查找了!

#!/usr/bin/env python3#The binary search functiondef Binary_Search(data_source,find_n): if len(data_source) >= 1: #判斷列表長度是否大於1,小於1就是一個值 mid = int(len(data_source)/2) #獲取列表中間索引;奇數長度列表長度除以2會得到小數,通過int將轉換整型 if find_n > data_source[-1]: #判斷查找值是否超出最大值 print('{}查找值不存在!'.format(find_n)) exit() elif find_n < data_source[0]: #判斷查找值是否超出最小值 print('{}查找值不存在!'.format(find_n)) exit() if data_source[mid] > find_n: #判斷列表中間值是否大於查找值 #print('查找值在 {} 左邊'.format(data_source[mid])) Binary_Search(data_source[:mid],find_n) #調用自己,並將中間值左邊所有元素做參數 elif data_source[mid] < find_n: #判斷列表中間值是否小於查找值 #print('查找值在 {} 右邊'.format(data_source[mid])) #調用自己,並將中間值右邊所有元素做參數 Binary_Search(data_source[mid:],find_n) else: print('找到查找值',data_source[mid]) #找到查找值 else: print('{}查找值不存在!'.format(find_n)) #特殊情況,返回查找不到Data = [22,12,41,99,101,323,1009,232,887,97]Data.sort() #列表從小到大排序Binary_Search(Data,323) #查找323執行結果:[root@localhost tmp]# ./binary_search.py 找到查找值 323好了,今天的知識就分享到這裡,歡迎關注愛編程的南風,私信關鍵詞:學習資料,獲取更多學習資源,如果文章對你有有幫助,請收藏關注,在今後與你分享更多學習python的文章。同時歡迎在下面評論區留言如何學習python。

相關焦點

  • Python遞歸函數、閉包和裝飾器
    目錄:一、遞歸函數二、閉包的深入講解三、裝飾器的使用一、 遞歸函數>遞歸函數recursion1.什麼是遞歸函數  函數直接或間接的調用自己本身可有解決循環上的問題import timedef 遞歸一定要控制遞歸的層數,當符合某一條件時要終止遞歸,幾乎所有的遞歸都能用while循環來代替。
  • python遞歸算法(上)
    什麼是遞歸在函數內部,是可以調用其他函數的。如果一個函數在內部調用自身,就稱這個函數就是遞歸函數。舉個例子:實現一個可以自定義重複列印你好的函數。因為遞歸函數是找到最小問題的解決方法,然後只要不斷使用這個方法就可以解決了,所以遞歸函數的優點是定義簡單,邏輯清晰。理論上,所有的遞歸函數都可以寫成循環的方式,但循環的邏輯不如遞歸清晰。下面就通過一些案例來實際感受一下。
  • Python內置函數、作用域、閉包、遞歸
    內嵌函數: 在函數內部定義函數,就是函數裡面嵌套函數閉包:一個閉包就是你調用了一個函數A,這個函數A返回了一個函數B給你。這個返回的函數B就叫做閉包。(但是B函數,一定要是嵌套在函數A裡面) 。你在調用函數A的時候傳遞的參數就是自由變量。總結:一個函數裡面嵌套一個函數,調用外面這個函數,就返回裡面嵌套的函數。
  • Python進階之遞歸函數的用法及其示例
    作者 | 程式設計師adny責編 | 徐威龍封圖| CSDN│下載於視覺中國出品 | AI科技大本營(ID:rgznai100)本篇文章主要介紹了Python進階之遞歸函數的用法及其示例,現在分享給大家,也給大家做個參考
  • python算法遞歸於尾遞歸!
    遞歸函數:在程式語言中,函數直接或間接調用函數本身,則該函數稱為遞歸函數;在數學上的定義如下:對於某一函數 f(x)f(x),其定義域是集合 A,那麼若對於 A 集合中的某一個值 X0X ,其函數值 f(x0)
  • Python 進階之遞歸函數一點都不難!
    出品 |  AI科技大本營(ID:rgznai100)本篇文章主要介紹了Python進階之遞歸函數的用法及其示例,現在分享給大家,也給大家做個參考
  • 【Python基礎】Python 函數使用總結
    ,目錄結構:Python 函數專題基礎部分1 函數組成2 引用傳參3 默認參數與關鍵字參數4 可變參數5 內置函數進階部分6 偏函數7 遞歸函數8 匿名函數9 高階函數10 嵌套函數總結可以先記住:修改內置函數的默認參數,就像內置函數int默認參數base等於10,使用偏函數調整默認base值為2.7 遞歸函數遞歸函數是指調用自身的函數。
  • JavaScript函數 - 遞歸
    什麼是函數遞歸?遞歸是指函數自己調用自己。注意:我們可以寫出遞歸,但是我們並不知道她如何得出結果。1.面試官如果問你遞歸的相關知識,面試官的水平還不錯。2.但是在工作中,一般情況下禁止使用遞歸。2.找這一次和上一次的關係3.假設當前的函數已經可以使用,調用自身計算上一次的運行結果,再寫出這次的運行結果特點:1.必須有參數2.必須由return返回值舉個小例子:計算1加到n的和分析:先來封裝一個函數為sun(n); 找出臨界值,如果n==1時,就直接return返回1
  • 【超詳細】一文學會遞歸解題
    本文試圖從以下幾個方面來講解遞歸力爭讓大家對遞歸的認知能上一個新臺階,特別會對遞歸的精華:時間複雜度作詳細剖析,會給大家總結一套很通用的求解遞歸時間複雜度的套路,相信你看完肯定會有收穫什麼是遞歸簡單地說,就是如果在函數中存在著調用函數本身的情況,這種現象就叫遞歸。
  • 少兒Python編程培訓手冊系列之——函數的定義及遞歸思想
    前面在C語言教學視頻中講過,函數是一段獨立的子程序,由相關代碼組成,可以重複多次使用。有了函數:模塊化編程,可以使代碼的層次更清晰。函數分系統函數(內置函數、內建函數)和自定義函數。01系統函數系統函數,也叫內置函數或內建函數,也就是軟體的開發人員已經定義好的函數,我們只需要拿過來使用就可以了。比如:print()、input()、range()等等這些函數。
  • 【ATLV系列】系統講解GH Python教程 — 遞歸09
    | 版權聲明| 作者:月之眼| 代碼作者:Satoru Sugihara| 原始碼連結:http://atlv.org/education/ghpython/| 本文首發於大水牛參數化設計平臺| 如需轉載請聯繫作者| 如果覺得文章不錯,歡迎分享  GH Python遞歸01 1:遞歸函數遞歸函數是指一個函數在內部調用自身的函數。
  • Python 函數3000字使用總結
    ,目錄結構:Python 函數專題基礎部分1 函數組成2 引用傳參3 默認參數與關鍵字參數4 可變參數5 內置函數進階部分6 偏函數7 遞歸函數8 匿名函數9 高階函數10 嵌套函數總結可以先記住:修改內置函數的默認參數,就像內置函數int默認參數base等於10,使用偏函數調整默認base值為2.7 遞歸函數遞歸函數是指調用自身的函數。
  • Python案例|混用C函數
    今天,我們來解決python的速度問題.我們平時所說的Python是指在PC機上的CPython,換句話說,Python的解釋器是用C語言編寫的。為Python編寫C擴展模塊能夠顯著提高Python的執行速度,眼下有好幾種方案可以完成這件事。
  • 高斯求和如何用遞歸實現,Python詳解遞歸那些事,看這1篇足夠!
    前段時間,有小夥伴留言,想了解一些有關Python遞歸函數的知識。最近,小編也惡補了一些這方面的內容。說實話,對於遞歸的認識過程,確實能讓我們從中探索出一些很有意思的內容。今天,我們來一探究竟。01什麼是遞歸?關於遞歸,網上有一大堆描述資料,很專業,也很詳細。感興趣的小夥伴可以隨手搜一搜。
  • 詳細講解:python中的lambda與sorted函數
    本文內容主要介紹了python中的lambda與sorted函數的相關資料,幫助大家更好的理解和學習python,感興趣的朋友可以了解下!!!
  • 「通俗易懂的文字」+「經典的案例」希望能讓你順利入門「遞歸算法」
    遞歸是非常常見的一種算法,非常經典,可以解決非常多的問題。但我估計雖然大部分人知道遞歸,也能看得懂遞歸,但在實際運用中,很容易被遞歸給搞暈(數據,變量,函數等來回的出棧入棧)。今天寫篇文章分享下,或許,能夠給你帶來一些幫助。
  • python中利用py幫助命令擴展開發知識面
    計算公式:(今天是星期幾+幾天後)%7本節知識視頻教程一、回到python REPLpython REPL簡稱python控制臺。2、利用help命令來獲取模塊下面的所有函數help(模塊名稱):返回所有的函數的詳細解釋。help(模塊名稱.函數名稱):返回某個函數的詳細解釋。
  • 第5章:函數式編程
    通過使用函數,我們可以避免在程序中一次又一次地重寫相同的邏輯/代碼。我們可以在程序中和程序中的任何地方多次調用python函數。當它被分成多個函數時,我們可以輕鬆地跟蹤大型python程序。可重用性是python函數的主要成就。但是,函數調用總是在python程序中開銷。
  • 了解和掌握Python的函數
    python用縮進控制語法,當if後面的條件為true時,程序才會執行縮進中的內容,否則跳過。縮進在Jupyter中用tab輸入,也可以使用四個空格。當條件為false時,可以用else,此時不會執行if中的內容,而是else。
  • 收藏:Python函數練習題
    3、 編寫一個名為 make_shirt() 的函數,它接受一個尺碼以及要印到 T 恤上的字樣。這個函數應列印一個句子,概要地說明 T 恤的尺碼和字樣。使用位置實參調用這個函數來製作一件 T 恤;再使用關鍵字實參來調用這個函數。