描述:
若是一個函數在內部調用自身自己,這個函數就是遞歸函數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)