本文章參考自廖雪峰的官方網站
Python--遞歸函數
一. 描述
1.編程語言中, 函數Func(Type a,......)直接或間接調用函數本身,則該函數稱為遞歸函數.
2.在數學上,關於遞歸函數的定義如下: 對於某一函數f(x), 其定義域是集合A, 那么若對於A集合中的某一個值x0,
其函數值f(x0)由f(f(x0))決定,那么就稱f(x)為遞歸函數.
3.遞歸的定義:一種計算過程, 如果其中每一步都要用到前一步或前幾步的結果, 稱為遞歸的.
用遞歸過程定義的函數, 稱為遞歸函數, 例如連加,連乘及階乘等. 凡是遞歸的函數, 是可計算的, 即能行的.
二. 實例說明
1. 例1:
計算階乘 n! = 1 * 2 * 3 * ... * n, 用函數fact(n)表示, 可以看出:
fact(n) = n! = 1 * 2 * 3 * ... * (n-1) * n = (n-1)! * n = fact(n-1) * n,
所以, fact(n)可以表示為n * fact(n-1), 只有n = 1時需要特殊處理.
於是, fact(n)用遞歸的方式寫出來就是:
def fact(n): if n == 1: return 1 return n * fact(n - 1) print(fact(5)) # 輸出結果: 120 print(fact(1)) # 輸出結果: 1
上面就是一個遞歸函數.
如果我們計算fact(5), 可以根據函數定義看到計算過程如下:
==> fact(5)
==> 5 * fact(4)
==> 5 * (4 * fact(3))
==> 5 * (4 * (3 * fact(2)))
==> 5 * (4 * (3 * (2 * fact(1))))
==> 5 * (4 * (3 * (2 * 1)))
==> 5 * (4 * (3 * 2))
==> 5 * (4 * 6)
==> 5 * 24
==> 120
遞歸函數的優點是定義簡單, 邏輯清晰. 理論上, 所有的遞歸函數都可以寫成循環的方式, 但循環的邏輯不如遞歸清晰.
使用遞歸函數需要注意防止棧溢出. 在計算機中, 函數調用是通過棧(stack)這種數據結構實現的,每當進入一個函數調用,
棧就會加一層棧幀, 每當函數返回, 棧就會減少一層棧幀. 由於棧的大小不是無限的, 所以, 遞歸調用的次數過多, 會導致
棧溢出. 當嘗試調用fact(1000)時, 程序會報錯.
總結來說,使用遞歸函數的優點是邏輯簡單清晰, 缺點是過深的調用會導致棧溢出(即比較占用內存空間).
2. 例2: 用遞歸函數來實現對樹形結構的遍歷是一種很好的方法!
以下實例為遍歷某文件夾內的所有文件和文件夾:
import os # 引入os模塊 def func(file_path, ceng): # 獲取到路徑下的所有文件 lst = os.listdir(file_path) # 得到文件夾里的所有文件和文件夾 for file in lst: # 遍歷文件夾 full_path = os.psth.join(file_path, file) # 獲取到文件的全路徑 if os.path.isdir(full_path): # 判斷這個路徑是否是一個文件夾 print("\t" * ceng, file) func(full_path, ceng + 1) else: print("\t" * ceng, file) else: return func("D:\Program Files\\feiq\Recv Files", 0) # 具體文件路徑可以根據自己的實際情況進行修改
總的來說, 遞歸函數的實質就是自己調用自己. 在下一次對自己的調用之前, 函數把參數值根據某種對應法則進行了改變, 從而將改變后的結果作為下一次調用的參數. 以上面的例子來說, 函數func的形參從(file_path, ceng)變成了(full_path, ceng + 1). 所以, 我們在使用遞歸函數時, 一定要明確, 什么是不變的(函數本身), 什么是變的(參數).
