[ python ] 遞歸函數

遞歸函數

 描述:
    若是一個函數在內部調用自身自己,這個函數就是遞歸函數python

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

 

首先,咱們能夠從字面上來理解遞歸函數

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

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

 

實例:google

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

(1)初級寫法:code

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

print(n)

 (2)中級寫法:blog

    使用 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]

 

附加題:

    使用遞歸函數求斐波拉契數列.

 

首先斐波拉契數列以下:

1,1,2,3,5,8

規律:從第三位開始,後面的值是前面兩個值的和

def fib(n):
    if n == 1 or n == 2:
        return 1
    return fib(n -1) + fib(n -2)

 

上面的函數是按照斐波拉契數列規律寫出來的,思路是沒問題的,可是在函數內部調用兩次自身函數,這樣的效率很是慢。
所以在使用遞歸函數時,必定要注意在函數內部只能調用一個,不然嚴重影響執行效率

上面的遞歸函數修改以下:

count = 0

def fib(n, a=0, b=1):
    # 每次遞歸獲取全局變量count = 0
    global count
    # 在遞歸函數中,count = 1
    count += 1
    # 當 1 < n -1 時,進行函數的遞歸計算
    if count < n-1:
        return fib(n, b, a+b)
    # 當 1 >= n -1 時,n = 2 或者 n =1 返回 a + b = 0 + 1 = 1
    elif count >= n -1:
        return a+b


f = fib(10)
print(f)
相關文章
相關標籤/搜索