python 淺談 遞歸函數
最近在自學一些python,找了些資料。自己慢慢研究到了遞歸函數這一章,碰到個很經典的例子。漢諾塔的移動。一開始嘗試自己寫的時候發現,這東西怎么可能寫的出來。但是看到別人寫出來以后發現,這東西真的能寫出來。
本着借鑒的目的想去分析一下別人寫的東西。覺得很有意思想給大家分享一下,如果有誤請大家指正首先大家可以先自己想想如何能寫出來。
先說一下:所謂的遞歸,我認為就是不斷重復調用。直到return 出當前的遞歸循環。在我拆分的過程中,大家不妨先自己想一下結果,然后看一下我執行出來的結果,是否和各位所想的一樣呢。
本文僅代表自己觀點,如有錯誤大家指出。
--------------python 環境為3.6.4-------- 下面為高手寫的代碼 def move(n, a, b, c): if n == 1: return print(a, '-->', c) move(n-1,a,c,b) move(1,a,b,c) move(n-1,b,a,c) ---------------------------------------------- 乍一看真的是一個很簡單很簡單的三層遞歸函數。我第一眼看到的時候都不認為這個是對的。但是真的執行以后發現。寫的很有道理。輸入2和3執行一下。發現都能出來。 >>> move(2,'a','b','c') a --> b a --> c b --> c >>> move(3,'a','b','c') a --> c a --> b c --> b a --> c b --> a b --> c a --> c 直接看,我的水平着實有限,所以打算拆開來一步步看。首先改寫一下格式。我准備一個一個拆分來看。首先將第一個遞歸拆出來。帶入幾個值看一下 def move(n, a, b, c): if n == 1: return print(a, '-->', c) move(n-1,a,c,b) >>> move(2,'a','b','c') a --> b >>> move(3,'a','b','c') a --> c >>> move(4,'a','b','c') a --> b >>> move(5,'a','b','c') a --> c 發現了一個很有意思的結果,就是在輸入值(n>=2)且n為偶數的時候 是 a --> b,輸入值為奇數的時候 是 a --> c。原因也很簡單。在這個函數做遞歸的時候,因為n不等於1,所以遞歸函數一直在自己調用自己。所以字符 b和c的位置一直在互換。 繼續拆分。 def move(n, a, b, c): if n == 1: return print(a, '-->', c) move(1,a,c,b) >>> move(2,'a','b','c') a --> b >>> move(3,'a','b','c') a --> b >>> move(4,'a','b','c') a --> b 因為,這個n值為定值1.所以無論n(n>=2)值輸入的值為何值都只會輸出 a --> b。 最后拆分最后一個遞歸函數來看, def move(n, a, b, c): if n == 1: return print(a, '-->', c) move(n-1,b,a,c) 根據之前的兩個可以想象得出來,這個函數在n(n>=2)輸入奇數和偶數的時候輸出值一定不一樣。這里不做多解釋。 >>> move(2,'a','b','c') b --> c >>> move(3,'a','b','c') a --> c >>> move(4,'a','b','c') b --> c >>> move(5,'a','b','c') a --> c 接着如果兩兩結合呢。 執行 n=2、3、4的結果分別為: def move(n, a, b, c): if n == 1: return print(a, '-->', c) move(n-1,a,c,b) move(1,a,b,c) >>> move(2,'a','b','c') a --> b a --> c >>> move(3,'a','b','c') a --> c a --> b a --> c >>> move(4,'a','b','c') a --> b a --> c a --> b a --> c 一旦開始兩兩結合。立馬開始變得復雜了,這里有如下幾種可能: 1 順序執行,即傳輸一個值進去,會完整的走一次函數。 如果傳輸值為2,執行move(2-1,a,c,b) return ,move1。輸出結果為如上所示。 如果傳輸值為3,則執行為,move(3-1,a,c,b) pass, move1, move(2-1,a,c,b) return, move(1,a,b,c)。 輸出結果為如上所示 如果傳輸值為4,則執行為,move(4-1,a,c,b) pass,move1,move(3-1,a,c,b),move1,move(2-1,a,c,b) return,move(1,a,b,c)。 這里很明顯輸出結果並不是這樣的。所以並不是順序執行這種。 2 組內循環,即傳輸一個n值進去,在帶有n值運算的條件中,一直循環調用到能return時再跳出當前循環。我認為這種方式應該是正確的。(句末帶--為輸出行,代碼中abc為實際代表的abc) 如輸入的n為2,則 move(2,a,b,c) move(1,a,c,b)--return跳出當前循環,abc值為上一層所變化的值 move(1,a,b,c)-- 輸出結果為: a-b a-c 輸入的n為3,則 move(3,a,b,c) move(3-1,a,c,b) move(3-1-1,a,b,c)--return跳出當前循環,abc值為上一層所變化的值 move(1,a,c,b)-- move(1,a,b,c) -- 輸出結果為 : a-c a-b a-c 輸入n為4,則 move(4,a,b,c) move(4-1,a,c,b) move(4-1-1,a,b,c) move(4-1-1-1,a,c,b)-return跳出當前循環,abc值為上一層所變化的值 move(1,a,b,c)-- move(1,a,c,b)-- move(1,a,b,c)-- 輸出結果為:a-b a-c a-b a-c 這類型的遞歸類似於一個凹字。 兩兩結合還有一種情況就是:這類的循環是先做一次 move1 然后做 move n 時會去調用move1。結果如下所示。 def move(n, a, b, c): if n == 1: return print(a, '-->', c) move(1,a,b,c) move(n-1,a,c,b) 執行 n=2、3、4的結果分別為: >>> move(2,'a','b','c') a --> c a --> b >>> move(3,'a','b','c') a --> c a --> b a --> c >>> move(4,'a','b','c') a --> c a --> b a --> c a --> b 這種情況下如果n為2 move(2,a,b,c) move(1,a,b,c)-- move(2-1,a,c,b)-- 輸出結果為:a-c a-b n為3則 move(3,a,b,c) move(1,a,b,c)-- move(3-1,a,c,b) move(1,a,c,b)-- move(3-1-1,a,b,c)-- 輸出結果為 a-c a-b a-c n為4則 move(4,a,b,c) move(1,a,b,c)-- move(4-1,a,c,b) move(1,a,c,b)-- move(4-1-1,a,b,c) move(1,a,b,c)-- move(4-1-1-1,a,c,b)-- 輸出結果為:a-c a-b a-c a-b 這類型的遞歸類似於一個不斷增加的直線(語言很笨拙) 兩兩結合最后一種情況如下所示:這類遞歸中,會先調用n-1,然后,后續函數在當前循環中使用n-1后的值。一組遞歸函數完成之后。重新調用第二組時,會重新初始化輸入值。 def move(n, a, b, c): if n == 1: return print(a, '-->', c) move(n-1,a,c,b) move(n-1,b,a,c) >>> move(2,'a','b','c') a --> b b --> c >>> move(3,'a','b','c') a --> c c --> b b --> a a --> c >>> move(4,'a','b','c') a --> b b --> c c --> a a --> b b --> c c --> a a --> b b --> c 如果輸入的n為2 move(2,a,b,c) move(2-1,a,c,b)--循環1 move(2-1,b,a,c)--循環2 輸出結果為: a-b a-c 如果輸入的n為3 move(3,a,b,c) move(3-1,a,c,b) move(3-1-1,a,b,c)-- move(3-1-1,b,a,c)--循環1結束 move(3-1,b,a,c)#2組循環開始循環2為2時 move(3-1-1,b,c,a)-- move(3-1-1,a,b,c)-- 輸出結果為:a-c b-c b-a c-a 如果輸入的n為4 move(4,a,b,c) move(4-1,a,c,b) move(4-1-1,a,b,c) move(4-1-1-1,a,c,b)-- move(4-1-1-1,b,a,c)-- move(4-1-1,c,a,b) move(4-1-1-1,c,b,a)-- move(4-1-1-1,a,c,b)--至此循環1結束 move(4-1,b,a,c)#2組循環開始循環2為3時 move(4-1-1,b,c,a) move(4-1-1-1,b,a,c)-- move(4-1-1-1,c,b,a)-- move(4-1-1,a,b,c)#組循環開始循環2為2時 move(4-1-1-1,a,c,b)-- move(4-1-1-1,b,a,c)-- 輸出結果為:a-b b-c c-a a-b b-c c-a a-b b-c 這類遞歸類似於波形。 個人認為我這種想法應該是對的,下面是我自己突發奇想想到的一個驗證我的這個想法的代碼。這個代碼中可以觀察n的各個階段中各個值的實際值。 def move(n, a, b, c): print(n,a, b, c) if n == 1: return print(a, '-->', c) move(n-1,a,c,b) move(1,a,b,c) 這個代碼可以在任意步驟顯示,a,b,c中的值。可以參考。 接下來就是三個遞歸函數的調用了。根據兩個遞歸函數的調用來猜測一下。 def move(n, a, b, c): if n == 1: return print(a, '-->', c) move(n-1,a,c,b) move(1,a,b,c) move(n-1,b,a,c) >>> move(2,'a','b','c') a --> b a --> c b --> c >>> move(3,'a','b','c') a --> c a --> b c --> b a --> c b --> a b --> c a --> c 如果n的傳輸值為2時 move(2,a,b,c) move(2-1,a,c,b)-- move(1,a,b,c)-- move(2-1,b,a,c)-- 如果n的傳輸值為3時 move(3,a,b,c) move(3-1,a,c,b) move(3-1-1,a,b,c)-- move(1,a,c,b)-- move(3-1-1,c,a,b)-- move(1,a,b,c)-- move(3-1,b,a,c) move(3-1-1,b,c,a)-- move(1,b,a,c)-- move(3-1-1,a,b,c)-- 詳細說一下這個吧:根據之前所說的, 1.首先帶入(3,a,b,c)進入遞歸調用,先走第一個小循環move(n-1,a,c,b),此時n=3。 小循環中走n=3-1條件不滿足,繼續走小小循環n=3-1-1 return,得出結果,退出當前小小循環。 用當前小循環中的值走(1,a,b,c)輸出一個值,然后用當前的n=2走小循環中的move(n-1,b,a,c)。走完之后。第一個小循環整體走完。 2.然后在整體遞歸函數中走move(1,a,b,c)。 3.然后開始move(n-1,b,a,c)的遞歸調用,同1類似。 move(4,a,b,c) move(4-1,a,c,b) move(4-1-1,a,b,c) move(4-1-1-1,a,c,b)-- move(1,a,b,c)-- move(4-1-1-1,b,a,c)-- move(1,a,c,b)-- move(4-1-1,c,a,b) move(4-1-1-1,c,b,a)-- move(1,c,a,b)-- move(4-1-1-1,a,c,b)-- move(1,a,b,c)-- move(4-1,b,a,c) move(4-1-1,b,c,a) move(4-1-1-1,b,a,c)-- move(1,b,c,a)-- move(4-1-1-1,c,b,a)-- move(1,b,a,c)-- move(4-1-1,a,b,c) move(4-1-1-1,a,c,b)-- move(1,a,b,c)-- move(4-1-1-1,b,a,c)--
以上是n=4時漢諾塔的具體執行步驟。我自己能寫明白。講就算了。大家自行理解吧。
寫在最后,可能我的語言表述並不是特別好,但是步驟寫的很清楚了。每一次的遞歸,遞歸調用遞歸,遞歸調用常函數。常函數調遞歸。以及各個階段值的變換。
因為我也才接觸python沒多久。都是自己在摸索。各位如有問題請及時聯系我。我再進行相應的修改。