遞迴(Python)
以下為此單元簡報。
https://drive.google.com/file/d/1J4vTn-eUJNcYX0hiNp2_wCdDGMvb3Vi0/view?usp=sharing
以下為本單元的教學影片(放置於YouTube上),以下兩種方式擇一。
(A)以YouTube播放清單方式觀看影片,如下。
https://www.youtube.com/playlist?list=PLOk9WKoJdjHbD6LYSDMQkGLfy0L0_WbtE
(B)以超連結方式觀看個別影片,如下。
Python進階-1-1-遞迴與PyCharm除錯(https://youtu.be/FZmofX_nCJc)
Python進階-1-2-遞迴範例(https://youtu.be/coh0EkJF9Ms)
在撰寫遞迴函式前,先要了解如何自訂函式。
(1)自訂函式
函式用於結構化程式,將相同功能的程式獨立出來,經由函式的呼叫,傳入資料與回傳處理後的結果,程式設計師只要將函式寫好,可以不斷利用此函式做相同動作,可以達成程式碼不重複,要修改此功能,只要更改此函式。
函式的定義、傳回值與呼叫
自訂函式需要包含兩個部分,分別是函式的定義與函式的呼叫。函式的定義是實作函式的功能,輸入參數與回傳處理後的結果;函式的呼叫用於讓自訂函式真正執行。
(1-1)函式的定義與傳回值
以下為函式的定義,函式名稱後接著一對小括號,小括號可以填入要傳入函式的參數,當參數有多個的時候以逗號隔開,函式名稱前面宣告函式回傳的資料型別,函式的範圍為函式名稱後縮行固定長度的空白鍵,當函式需要傳回值使用指令return,表示程式執行的控制權由函式轉換為原呼叫函式,函式的定義與傳回值格式,如下。
函式定義與傳回值的語法
def 函式名稱(參數1, 參數2 , …):
函式的敘述區塊
return 要傳回的變數或值
函式定義與傳回值的範例
(1-2)函式的呼叫
程式經由函式呼叫,將資料傳入函式,函式處理後傳回結果給呼叫程式
方法一:無傳回值的呼叫語法,利用函式名稱與參數來呼叫函式。
函式名稱(參數值1,參數值2,…)
方法二:有傳回值的呼叫語法,等號右邊要先做完,利用函式名稱與參數來呼叫函式,最後函式回傳值給變數,變數就紀錄函式呼叫後的回傳值。
變數=函式名稱(參數值1,參數值2,…)
(1-3)函式範例
以下範例用於計算長方形面積,使用者輸入長度與寬度,呼叫自訂的computeArea函式將長度與寬度傳入,回傳計算結果。
(2)遞迴
遞迴是有趣的程式設計技巧,函式執行過程中呼叫自己,稱作遞迴。而這樣的自己呼叫自己,需要有終止的條件,若沒有終止的條件就會形成無窮遞迴。
(2-1)求解n階乘(n!)
數學上定義n階乘為「n!=n*(n-1)*(n-2)*(n-3)*…*3*2*1」。我們可以分階段看,求解n階乘,可以分解成n乘以(n-1)階乘意即「n!=n*(n-1)!」,求解(n-1)階乘,可以分解成n-1乘以(n-2)階乘意即「(n-1)!=(n-1)*(n-2)!」,求解(n-2)階乘,可以分解成n-2乘以(n-3)階乘意即「(n-2)!=(n-2)*(n-3)!」,依此類推,直到求解3階乘,可以分解成3乘以2階乘意即「3!=3*2!」,求解2階乘,可以分解成2乘以1階乘意即「2!=2*1!」,1!就不用再往下求解直接就是1,這樣一層一層遞迴下去直到求解1!,就終止遞迴,再一層一層往上回推,如下圖。
寫成遞迴函式,假設函式名稱為f(n)為求解n!,f(n-1)為求解(n-1)!,代入上圖。
程式碼
(2-2)m的n次方
求解m的n次方之值,數學上定義m的n次方為n個m相乘。我們可以分階段看,求解m的n次方,可以分解成m乘以m的n-1次方意即「m^n=m*m^(n-1)」,求解m的n-1次方,可以分解成m乘以m的n-2次方意即「m^(n-1)=m*m^(n-2),求解m的n-2次方,可以分解成m乘以m的n-3次方意即「m^(n-2)=m*m^(n-3),依此類推,直到求解m的3次方,可以分解成m乘以m的2次方意即「m^3=m*m^2」,m的2次方,可以分解成m乘以m的1次方意即「m^2=m*m^1」,m^1就不用再往下求解直接就是m,這樣一層一層遞迴下去直到求解m^1,就終止遞迴,再一層一層往上回推,如下圖。
寫成遞迴函式,假設函式名稱為p(n)為求解m^n,p(n-1)為求解m^(n-1),代入上圖。
程式碼
(2-3)費氏數列
費氏數列為一個特殊數列,符合以下規則,這樣的關係也是遞迴關係,可以使用遞迴函式求解費氏數列的第k個元素。
k等於0或1時,F(k)等於1,k大於1時,F(k)等於F(k-1)與F(k-2)相加。
以下為求解F(4)的遞迴呼叫過程,箭頭上方的括弧數字表示程式執行的順序,箭頭向下表示遞迴呼叫,箭頭向上表示回傳值給呼叫函式,如下圖。
求解F(4)的過程
(1) 求解F(4)先遞迴呼叫求F(3)。
(2) 求解F(3)先遞迴呼叫求F(2)。
(3) 求解F(2)先遞迴呼叫求F(1)。
(4) F(1)值為1,所以回傳F(1),即回傳1。
(5) 求解F(2),F(1)已經有解,但需要再遞迴呼叫求F(0)。
(6) F(0)值為1,所以回傳F(0),即回傳1。
(7) 求解F(2),F(1)與F(0)已經求得,F(2)=F(1)+F(0)=1+1=2,回傳F(2),即回傳2。
(8) 求解F(3),F(2)已經有解,但需要再遞迴呼叫求F(1)。
(9) F(1)值為1,所以回傳F(1),即回傳1。
(10) 求解F(3),F(2)與F(1)已經求得,F(3)=F(2)+F(1)=2+1=3,回傳F(3),即回傳3。
(11) 求解F(4),F(3)已經有解,但需要再遞迴呼叫求F(2)。
(12) 求解F(2)先遞迴呼叫求F(1)。
(13) F(1)值為1,所以回傳F(1),即回傳1。
(14) 求解F(2),F(1)已經有解,但需要再遞迴呼叫求F(0)。
(15) F(0)值為1,所以回傳F(0),即回傳1。
(16) 求解F(2),F(1)與F(0)已經求得,F(2)=F(1)+F(0)=1+1=2,回傳F(2),即回傳2。
求解F(4),F(3)與F(2)已經求得,F(4)=F(3)+F(2)=3+2=5,如此得到結果。
程式碼
(2-4) 8queue問題
將西洋棋的8個queen放在8x8的棋盤上,如何擺放讓8個queen彼此互相不衝突,請找出在8x8棋盤上放上8個queen,而彼此相互不衝突的所有解。西洋棋的queen對於同一列、同一行與兩個對角線可以吃掉對方,如下圖,Q表示放置queen在第3列第4行,X表示這些位置不能放其他queen會發生衝突。
輸入說明
本題不需要輸入。
輸出說明
輸出所有可能的解,先輸出目前是第幾個解,接著輸出8個queen在8x8的棋盤上所在位置,每一行一定會有一個queen,請由第1行到第8行順序,依序輸出8個queen所在列的編號,舉例如下,下表中8個queen彼此互相不衝突,以「1 5 8 6 3 7 2 4」表示此8個queen在棋盤由左到右所在的列編號。
輸入範例
無須輸入。
輸出範例
全部共有92個解,輸出後面12個。
81 7 1 3 8 6 4 2 5
82 7 2 4 1 8 5 3 6
83 7 2 6 3 1 4 8 5
84 7 3 1 6 8 5 2 4
85 7 3 8 2 5 1 6 4
86 7 4 2 5 8 1 3 6
87 7 4 2 8 6 1 3 5
88 7 5 3 1 6 8 2 4
89 8 2 4 1 7 5 3 6
90 8 2 5 3 1 7 4 6
91 8 3 1 6 2 5 7 4
92 8 4 1 3 6 2 7 5
解題想法
使用遞迴方式暴力找出所有可能性,需要陣列s儲存8個queen所在的列編號。
Python程式碼
(2-5)組合(樂透包牌)
給定n個數字,n個數字都不相同,從n個數字中取出m個數字的所有可能性,輸出的m個數字請按照由小到大輸出,不要重複輸出,保證m小於等於n。
輸入說明
輸入整數n,之後接著輸入n個數字,接著輸入整數m,表示要從n個數字中選取m個。
輸出說明
從n個數字中取出m個數字的所有可能性,輸出的m個數字請按照由小到大輸出,不要重複輸出。。
輸入範例
6
23 41 34 5 17 22
5
輸出範例
5 17 22 23 34
5 17 22 23 41
5 17 22 34 41
5 17 23 34 41
5 22 23 34 41
17 22 23 34 41
解題想法
使用遞迴方式暴力找出所有可能性,需要陣列num儲存輸入的n個數字,陣列step儲存包牌的在陣列num的索引值。遞迴需要兩個參數,一個控制目前選取幾個(currStep),一個控制目前的起始元素讓選取的元素不重複。
Python程式碼
(2-6) 排列
給定n個相異的數字,請列出此n個數字的所有排列可能性。
輸入說明
輸入整數n,之後接著輸入n個數字。
輸出說明
列出n個數字的所有排列可能性。
輸入範例
3
5 4 6
輸出範例
4 5 6
4 6 5
5 4 6
5 6 4
6 4 5
6 5 4
解題想法
使用遞迴方式暴力找出所有排列的可能性,需要陣列num儲存輸入的n個數字,陣列step的元素儲存數字對應的陣列num索引值。遞迴需要一個參數,控制目前排列到第幾個(currStep)。
Python程式碼