RuntimeError: maximum recursion depth exceeded 遞歸深度報錯

我想把Python中的遞歸作爲一個專題討論一下。我在學習的時候,嘗試使用“Python遞歸”作爲關鍵詞,在Google和百度中搜索,發現結果大部分是對某個具體例子的遞歸應用討論,而對我這樣的小白來說,切入點有點高。而我現在需要做的,是從基礎概念開始。

想到討論遞歸問題,是因爲那個著名的“字典序”問題,但還是先從最基本的遞歸概念開始。我希望我討論完了這個,自己對遞歸也有一個基本的瞭解了。

遞歸的概念很簡單,如果函數包含了對其自身的調用,該函數就是遞歸的。拗口一點的定義是,如果一個新的調用能在相同過程中較早的調用結束之前開始,那麼個該過程就是遞歸。兩個定義都來自《Python核心編程第二版》的第304頁。

該書應用了一個經典的例子,來講述遞歸的應用:

階乘函數的定義是:
N! = factorial(N) = 1 * 2 * 3 * ... * N

那麼可以用這種方法來看階乘函數:
factorial(N) = N!
             = N * (N - 1)!
             = N * (N - 1) * (N - 2)!
             = N * (N - 1) * (N - 2) * ... * 3 * 2 * 1
             = N * factorial(N - 1)

於是我們有了階乘函數的遞歸版本:

def factorial(n):
    if n == 0 or n == 1: return 1
    else: return (n * factorial(n - 1))

print factorial(6)

可以很輕易的得到,6!的結果是720。

上面就是這本書關於遞歸的內容了,但關於Python的遞歸不僅僅就這麼一點知識吧?

來看看這個問題:

還是這個函數factorial(N),讓我們試試N = 999和N = 1000,問題來了,N = 999時能輸出正確答案,但當N = 1000時,就出現下面的錯誤了:
RuntimeError: maximum recursion depth exceeded
於是,請記住,默認的Python有一個可用的遞歸深度的限制,以避免耗盡計算機中的內存。在我的電腦,這是1000。

接着再來看看在我的第一個遞歸程序中遇到的問題:

爲了試試手,我打算寫一個簡單函數計算那個傳統的問題,1 + 2 + 3 + ... + 100,當然,必須使用遞歸來做:

我的腳本是這樣的,結果出錯了。

>>> def add(n):
...     if n > 0: return n + add(n - 1)
...
>>> add(100)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 2, in add
  File "<stdin>", line 2, in add
  ...
  File "<stdin>", line 2, in add
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'

上網問了之後,得知應該這樣寫這個腳本:
>>> def add(n):
... if n <= 0: return 0  
... if n > 0: return n + add(n - 1)
...     
>>> add(100)
5050

我本意想偷懶,在n小於等於零的時候,不返回任何東西。但Pyhton所做的和我想的卻不一樣。如果一個Python函數被設計成不返回任何東西,它會返回None。

這裏插入一些關於遞歸的網方解釋,因爲我是從網上搜到的這些內容:
(1)遞歸就是在過程或函數裏調用自身;
(2)在使用遞歸策略時,必須有一個明確的遞歸結束條件,稱爲遞歸出口。

遞歸算法一般用於解決三類問題:
(1)數據的定義是按遞歸定義的。(比如Fibonacci函數)
(2)問題解法按遞歸算法實現。(回溯)
(3)數據的結構形式是按遞歸定義的。(比如樹的遍歷,圖的搜索)   

遞歸的缺點:遞歸算法解題的運行效率較低。在遞歸調用的過程當中系統爲每一層的返回點、局部量等開闢了棧來存儲。遞歸次數過多容易造成棧溢出等。

【推薦】網文《精通遞歸程序設計》:
http://www.ibm.com/developerworks/cn/linux/l-recurs.html

遞歸程序的基本步驟,來自上面這篇文章

每一個遞歸程序都遵循相同的基本步驟: 
1.初始化算法。遞歸程序通常需要一個開始時使用的種子值(seed value)。要完成此任務,可以向函數傳遞參數,或者提供一個入口函數,這個函數是非遞歸的,但可以爲遞歸計算設置種子值。 
2.檢查要處理的當前值是否已經與基線條件相匹配(base case)。如果匹配,則進行處理並返回值。 
3.使用更小的或更簡單的子問題(或多個子問題)來重新定義答案。 
4.對子問題運行算法。 
5.將結果合併入答案的表達式。 
6.返回結果。

基線條件(base case)。基線條件是遞歸程序的最底層位置,在此位置時沒有必要再進行操作,可以直接返回一個結果。所有遞歸程序都必須至少擁有一個基線條件,而且必須確保它們最終會達到某個基線條件;否則,程序將永遠運行下去,直到程序缺少內存或者棧空間。

自己總結了一下,要寫一個遞歸的程序,需要這樣做:
1.一個基線條件。請在遞歸函數的一開始就處理這個基線條件。
2.一系列的規則,使對遞歸函數的每次調用都趨進於直至達到這個基線條件。

好吧,現在看來,基本概念已經差不多了,在回到“字典序”問題之前,先看看這個全排列的問題。題目要求很簡單,輸入n個數,能自動打印出全排列(Permutation)。比如輸入1,2,3,那它的全排列就是123,132,213,231,312,321。

 

 

RuntimeError: maximum recursion depth exceeded

  在網上查了,發現python默認的遞歸深度是很有限的,大概是900多的樣子,當遞歸深度超過這個值的時候,就會引發這樣的一個異常。

解決的方式是手工設置遞歸調用深度,方式爲

 

  1. import sys   
  2. sys.setrecursionlimit(1000000#例如這裏設置爲一百萬  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章