python 全棧開發,Day15(遞歸函數,二分查找法)

1、遞歸函數

江湖上流傳這這樣一句話叫作:人理解循環,神理解遞歸。因此你可別小看了遞歸函數,不少人被攔在大神的門檻外這麼多年,就是由於沒能領悟遞歸的真諦。python

 

遞歸函數:在一個函數裏執行再調用這個函數自己。
遞歸的默認最大深度:998linux

 

舉例,先來一個死循環算法

def func1():
    print(666)

while True:
    func1()

執行輸出:函數

666性能

...測試

 

遞歸函數spa

def func1():
    print(666)
    func1()

func1()

執行輸出:日誌

666code

...orm

RecursionError: maximum recursion depth exceeded while calling a Python object

 

那麼它是執行到多少次時,報錯呢?

加一個計數器

count = 0
def func1():
    global count
    count += 1
    print(count)
    func1()

func1()

執行輸出:

1

...

998

...

RecursionError: maximum recursion depth exceeded while calling a Python object

 

說明默認遞歸深度爲998

這個遞歸深度是能夠改的

import sys
#更改默認遞歸深度
sys.setrecursionlimit(100000)

count = 0
def func1():
    global count
    count += 1
    print(count)
    func1()

func1()

執行輸出:

...

3807

 

個人wind10系統,深度只能到這麼多

至於實際能夠達到的深度就取決於計算機的性能了

linux系統,深度可以達到更深。好比說1000萬,我開虛擬機測試的。

不過咱們仍是不推薦修改這個默認的遞歸深度,由於若是用997層遞歸都沒有解決的問題要麼是不適合使用遞歸來解決要麼是你代碼寫的太爛了

 

舉個例子來講明遞歸能作的事情

如今大家問我,alex老師多大了?我說我不告訴你,但alex比 egon 大兩歲。

你想知道alex多大,你是否是還得去問egon?egon說,我也不告訴你,但我比武sir大兩歲。

你又問武sir,武sir也不告訴你,他說他比太白大兩歲。

那你問太白,太白告訴你,他18了。

這個時候你是否是就知道了?alex多大?

1 金鑫   18
2 武sir   20
3 egon   22
4 alex    24
你爲何能知道的?

首先,你是否是問alex的年齡,結果又找到egon、武sir、太白,你挨個兒問過去,一直到拿到一個確切的答案,而後順着這條線再找回來,才獲得最終alex的年齡。這個過程已經很是接近遞歸的思想。咱們就來具體的我分析一下,這幾我的之間的規律。

age(4) = age(3) + 2 
age(3) = age(2) + 2
age(2) = age(1) + 2
age(1) = 40

那這樣的狀況,咱們的函數怎麼寫呢?

def age(n):
    if n == 1:
        return 40
    else:
        return age(n-1)+2

print(age(4))

執行輸出: 24

 

2、二分查找法

二分查找也稱折半查找(Binary Search),它是一種效率較高的查找方法。可是,折半查找要求線性表必須採用順序存儲結構,並且表中元素按關鍵字有序排列

 

算法有不少種,好比:二分查找,樹運算,堆,棧....

 

使用二分查找的前提

數據是有序且惟一的數字數列

 

舉例:

有一個列表,須要查找出索引爲66的值

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]

第一種方法: 直接使用index查詢

print(l.index(66))

執行輸出:17

 

第二種方法: 使用for循環

count = 0
for i in l:
    if i == 66:
        print(count)
        break
    count += 1

執行輸出,效果同上

 

假如咱們這個列表特別長,裏面好好幾十萬個數,效率過低了

須要使用二分查找算法

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]

你觀察這個列表,這是否是一個從小到大排序的有序列表呀?
若是這樣,假如我要找的數比列表中間的數還大,是否是我直接在列表的後半邊找就好了?

這就是二分查找算法!
那麼落實到代碼上咱們應該怎麼實現呢?
簡單版二分法

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 func(l,aim):
    mid = (len(l)-1)//2
    if l:
        if aim > l[mid]:
            func(l[mid+1:],aim)
        elif aim < l[mid]:
            func(l[:mid],aim)
        elif aim == l[mid]:
            print("bingo",mid)
    else:
        print('找不到')
        
func(l,66)
func(l,6)

執行輸出:

bingo 0
找不到

 

升級版二分法

把列表改短一點

li = [2, 3, 5, 10, 15, 33, 55]
def two_search(li, aim, start=0, end=None):
    end = len(li) if end is None else end
    mid_index = (end - start) // 2 + start  # 3
    if start <= end:
        if li[mid_index] < aim:
            return two_search(li, aim, start=mid_index+1, end=end)
        elif li[mid_index] > aim:
            return two_search(li, aim, start=start, end=mid_index-1)  #([2,3,5],3)
        elif li[mid_index] == aim:
            return mid_index
        else:
            return '沒有此值'
    else:
        return '沒有此值'
print(two_search(li,15))

執行輸出:

4

 

加日誌執行:

li = [2, 3, 5, 10, 15, 33, 55]
count = 0
def two_search(li, aim, start=0, end=None):
    global count
    count += 1
    end = len(li)-1 if end is None else end #結束索引值,len(li)要減1,不然若是傳100,函數沒法結束。
    mid_index = (end - start) // 2 + start  #中間索引

    #加日誌
    print('第{}執行,start的值爲{},end的值爲{},mid_index的值爲{},li[mid_index]的值爲{}'.format(count, start,end,mid_index,li[mid_index]))

    if start <= end:
        if li[mid_index] < aim:
            return two_search(li, aim, start=mid_index+1, end=end)
        elif li[mid_index] > aim:
            return two_search(li, aim, start=start, end=mid_index-1)  #([2,3,5],3)
        elif li[mid_index] == aim:
            return mid_index
        else:
            return '沒有此值'
    else:
        return '沒有此值'

print(two_search(li,15))

執行輸出:

第1執行,start的值爲0,end的值爲6,mid_index的值爲3,li[mid_index]的值爲10
第2執行,start的值爲4,end的值爲6,mid_index的值爲5,li[mid_index]的值爲33
第3執行,start的值爲4,end的值爲4,mid_index的值爲4,li[mid_index]的值爲15
4

 

解釋:

先來解釋一下中間索引  // 表示取整除 - 返回商的整數部分 ,好比9//2 輸出結果 4

列表有7個元素,第一次,執行時,start爲0,(end - start) // 2 + start 等於(6-0)//2 + 0    mid_index的值爲3,li[mid_index]的值爲10

那麼爲何mid_index的等式,後面要加start呢?

第一次執行時,中間索引值,取3

第二次執行時,中間索引值,取5

若是不加start,那麼第二隻仍是取的是3

 

第一次執行時,li[mid_index]的值爲10,aim的值爲15,進入if判斷,走第一個if條件

 由於找到的中間值比目標值要小,因此start要在mid_index的基礎上加1

通俗的來說,劈一半,左邊找的值過小,就找右邊,因此要加1

 

第二次執行時,li[mid_index]的值爲33,aim的值爲15,進入if判斷,走第二個if條件

由於找到的中間值比目標值要大,start依然不變,end要在mid_index的基礎上減1

通俗的來說,再劈一半,左邊找的值太大,就繼續找左邊的,因此要減1

 

第三次執行時,li[mid_index]的值爲15,aim的值爲15,進入if判斷,走第三個if條件

已經找到目標值了,直接return mid_index 結束整個函數

相關文章
相關標籤/搜索