1. 程式人生 > >[ python ] 遞歸函數

[ python ] 遞歸函數

自身 所有 {} 遍歷 運算 小數 基礎 代碼執行 內部

遞歸函數

描述:
如果一個函數在內部調用自身本身,這個函數就是遞歸函數

遞歸函數特性:
(1)必須有一個明確的結束條件
(2)每次進入更深一層遞歸時,問題規模相比上次遞歸都應有所減少
(3)相鄰兩次重復之間有緊密的聯系,前一次要為後一次做準備
(4)遞歸效率不高,遞歸層次過多會導致溢出

首先,我們可以從字面上來理解遞歸函數

遞:傳遞出去的意思
歸:回來的意思

遞歸函數就是一個有去有回的過程,以下一個簡單的例子來解釋遞歸函數:

實例:

計算一個10以下(包括10)整數的加法運算:

(1)初級寫法:

n = 0
for i in range(11):
    n += i

print(n)

(2)中級寫法:

使用 reduce 高階函數進行累計運算

from functools import reduce
print(reduce(lambda x, y: x+y, range(11)))

(3)遞歸函數的寫法:

def add(n):
    if n == 1:
        return n
    else:
        return n + add(n -1)

print(add(10))

這三種方法,顯然第二種是最簡單的,但是這裏是為了研究遞歸函數的用法,要了解遞歸函數的工作流程,就需要分解遞歸函數。

這裏只是為了說明問題,調用 add(5) :

def add(n):     # n = 5
    if n == 1:
        return n
    else:
        return n + add(n -1)    # 5 + add(5 -1)

def add(n):    # add(4)
    if n == 1:
        return n
    else:
        return n + add(n -1)    # 4 + add(4 -1)

def add(n):    # add(3)
    if n == 1:
        return n
    else:
        return n + add(n -1)    # 3 + add(3 -1)

def add(n):    # add(2)
    if n == 1:
        return n
    else:
        return n + add(n -1)    # 2 + add(2 -1)
    
def add(n):    # add(1)
    if n == 1:  # n = 1
        return n    # return 1
    else:
        return n + add(n -1)

以上是我們通過代碼執行流程分解出來的過程信息。
每當函數內部調用自身的時候,外部函數掛起,執行內部函數,當內部函數執行完畢,然後在執行外部函數;

用簡單的圖形來表示,如下:

===> add(5)
===> 5 + add(4)
===> 5 + (4 + add(3))
===> 5 + (4 + (3 + add(2)))
===> 5 + (4 + (3 + (2 + add(1))))
===> 5 + (4 + (3 + (2 + 1)))
===> 5 + (4 + (3 + 3))
===> 5 + (4 + 6)
===> 5 + 10
===> 15

遞歸函數的優點是定義簡單,邏輯清晰。理論上,所有的遞歸函數都可以寫成循環的方式,但循環的邏輯不如遞歸清晰。

實例:

  使用遞歸函數實現一個三級菜單的效果

技術分享圖片
menu = {
    北京: {
        海澱: {
            五道口: {
                soho: {},
                網易: {},
                google: {}
            },
            中關村: {
                愛奇藝: {},
                汽車之家: {},
                youku: {},
            },
            上地: {
                百度: {},
            },
        },
        昌平: {
            沙河: {
                北航: {},
            },
            天通苑: {},
            回龍觀: {},
        },
        朝陽: {},
        東城: {},
    },
    上海: {
        閔行: {
            "人民廣場": {
                炸雞店: {}
            }
        },
        閘北: {
            火車戰: {
                攜程: {}
            }
        },
        浦東: {},
    },
    山東: {},
}
三級菜單

提示:在編寫遞歸函數的時候要牢記以下三點:

(1)必須有一個明確的結束條件
(2)當數據按照一定規律執行的時候,才能考慮遞歸實現
(3)只有調用自身的函數才是遞歸函數

技術分享圖片
def treeML(dic):
    while True:
        for i in dic:
            print(i)

        key = input(>>>).strip()
        if key == q or key == b:
            return key
        elif key in dic:
            res = treeML(dic[key])
            if res == q:
                return q

treeML(menu)
遞歸函數實現三級菜單

二分查找算法與遞歸函數

二分查找算法:
簡單來講,就是一半一半的找。

二份查找實例:
有這樣一個數列:
1,2,3,4,5

當我們想要查找數字:4

原始的辦法:
從數列中一個一個遍歷,直到找到 4 為止,查找了 4 次。

二分查找算法:

首先切一半得到:3,因為 3< 4 我們獲取右半邊的數列 4, 5
然後我們在切一半得到:4,4=4,在二分算法中,我們一共就找了 2 次就得到結果。

技術分享圖片

當我們想的多了,總結出更加便捷的方式,計算機才能更加高效的工作;

現在通過遞歸函數來實現,二分查找算法:

數列:
l = [2,3,5,10,15,16,18,22,26,30,32,35,41,42,43,55,56,66,67,69,72,76,82,83,88]

查找序列中是否有數字:83

l = [2,3,5,10,15,16,18,22,26,30,32,35,41,42,43,55,56,66,67,69,72,76,82,83,88]

基礎實現:
def find(l, aim):
    mid_index = len(l) // 2    # 這裏需要取整數不能是小數
    if l[mid_index] > aim:  # 當取的值大於要找的值,取左邊
        find(l[:mid_index], aim)    # 通過切片取list左邊的值
    elif l[mid_index] < aim:    # 當取的值大於要找的值,取右邊
        find(l[mid_index+1:], aim)    # 通過切片取list右邊的值
    else:
        print(mid_index, l[mid_index])  # 數字比較只有三種情況,大於、小於、等於

find(l, 82)

上面的實例,雖然找到序列中含有 82 但是 索引位置是有問題的。修改如下:

l = [2,3,5,10,15,16,18,22,26,30,32,35,41,42,43,55,56,66,67,69,72,76,82,83,88]


def find(l, aim, start=None, end=None):
    start = start if start else 0
    end = len(l) -1 if end is None else end
    mid_index = (end - start) // 2 + start
    if start > end:
        return None
    if l[mid_index] > aim:
        return find(l, aim, start, mid_index-1)
    elif l[mid_index] < aim:
        return find(l, aim, mid_index+1, end)
    elif l[mid_index] == aim:
        return mid_index, l[mid_index]

res = find(l, 82)
print(res)

# 執行結果:
# (22, 82)

以上遞歸函數,比較疑惑的地方:
end = len(l)-1 if end is None else end
這裏為什麽:len(l)-1

分析結果如下:
提示:如果要對遞歸函數進行分析,需要將代碼執行流程分解開,查看就更加明顯了。

l = [2,3,5]


def find(l, aim, start=None, end=None):
    start = start if start else 0   # start = 0
    end = len(l) if end is None else end # end = 3
    mid_index = (end - start) // 2 + start  # mid_index = (3-0) // 2 + 0 =1
    if start > end:
        return None
    if l[mid_index] > aim:
        return find(l, aim, start, mid_index-1)
    elif l[mid_index] < aim:    # 3 < 100
        return find(l, aim, mid_index+1, end)   # find(l, 6, 2, 3)
    elif l[mid_index] == aim:
        return mid_index, l[mid_index]

--------------------------------------------------------------------------------------------
通過第一步我們獲取到:
	find(l, 6, start=2, end=3)
	l最大的索引為:2

--------------------------------------------------------------------------------------------
		
def find(l, aim, start=None, end=None): # find(l, 6, 2, 3)
    start = start if start else 0   # start = 2
    end = len(l) if end is None else end # end = 3
    mid_index = (end - start) // 2 + start  # mid_index = (3-2) // 2 + 2 =2
    if start > end:
        return None
    if l[mid_index] > aim:
        return find(l, aim, start, mid_index-1)
    elif l[mid_index] < aim:    # 5 < 6
        return find(l, aim, mid_index+1, end)   # find(l, 6, 3, 3)
    elif l[mid_index] == aim:
        return mid_index, l[mid_index]
		
--------------------------------------------------------------------------------------------
通過第二步我們獲取到:
	find(l, 6, start=3, end=3)
	l最大的索引為:2

--------------------------------------------------------------------------------------------

def find(l, aim, start=None, end=None): # find(l, 6, 3, 3)
    start = start if start else 0   # start = 3
    end = len(l)-1 if end is None else end # end = 3
    mid_index = (end - start) // 2 + start  # mid_index = (3-3) // 2 + 3 = 3
    if start > end:
        return None
    if l[mid_index] > aim:  # l 最大的索引為:2 這裏:l[3] 報錯啦,因此 end = len(l)-1 if end is None else end
        return find(l, aim, start, mid_index-1)
    elif l[mid_index] < aim:
        return find(l, aim, mid_index+1, end)   # find(l, 6, 3, 3)
    elif l[mid_index] == aim:
        return mid_index, l[mid_index]

[ python ] 遞歸函數