《圖解算法》python實現的基本算法之二分法

引入

情景一: 假設要在電話簿中找一個名字以K打頭的人,可以從頭開始翻頁,直到進入以K打頭的部分。但你很可能不這樣做,而是從中間開始,因爲你知道以K打頭的名字在電話簿中間。

情景二: 假設要在字典中找一個以O打頭的單詞,你也將從中間附近開始。

情景三: 假設你登錄QQ。當你這樣做時,QQ必須覈實你是否有其網站的賬戶,因此必須在其數據庫中查找你的用戶名。如果你的用戶名爲kkk,QQ可從以A打頭的部分開始查找,但更合乎邏輯的做法是從中間開始查找。

總結: 以上查找是一個算法問題,幾乎每個類似的情景都可以使用按順序查找的方式,但是大多數情況下,這是不合理的,由此可以引申出二分或者說折半查找的算法。

二分查找

二分查找是一種算法,其輸入是一個有序的元素列表(必須是有序的)。如果要查找的元素包含在列表中,二分查找返回其位置;否則返回null。

舉一個猜數字的例子

1. 給你一個數組,長度爲100,從1開始到100結束:

在這裏插入圖片描述
2. 系統隨機產生一個範圍在1到100的正整數

3. 用戶來猜這個數字的大小,大了程序返回猜大了,反之返回猜小了,知道用戶猜中,程序結束。

參考代碼:

import random as r

def guess():
    n = 0
    rmin = 1
    rmax = 100
    ranNum = r.randint(rmin,rmax)
    while True:
        n += 1
        userNum = int(input(f'請猜一個範圍[{rmin},{rmax}]的整數:'))
        if ranNum == userNum:
            print(f'猜對了!<{userNum}>,猜了{n}次')
            break
        elif userNum > ranNum:
            print(f'猜大了!<{userNum}>')
        elif userNum < ranNum:
            print(f'猜小了!<{userNum}>')
            
if __name__ == '__main__':
    guess()

一種做法:
在這裏插入圖片描述

像上圖中這樣從小到大猜100次,這顯然是一種糟糕的方法。

如果我們採用二分法思想呢,每次都猜中間數。

在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述

在這裏插入圖片描述
不難發現,如果使用二分查找,每次查找的元素個數都會減一半,大大提高了效率,猜數字1到100,最多不超過7次,就能猜中答案!

優化後的猜數字代碼:

import random as r

def guess():
    n = 0
    rmin = 1
    rmax = 100
    ranNum = r.randint(rmin,rmax)
    while True:
        n += 1
        #機器算法,折半查找
        userNum = (rmin + rmax) // 2
        if ranNum == userNum:
            print(f'猜對了!<{userNum}>,猜了{n}次')
            break
        elif userNum > ranNum:
            rmax = userNum
            print(f'猜大了!<{userNum}>')
        elif userNum < ranNum:
            rmin = userNum
            print(f'猜小了!<{userNum}>')

if __name__ == '__main__':
    guess()

二分法例題

輸入:一個有序列表,一個待查找數
輸出:位置索引或者none

def binary_search(list,item):
    low = 0#low和high用於跟蹤要在其中查找的列表部分
    high = len(list) - 1
    while low <= high:
        mid = (low + high) // 2 #只要範圍沒有縮小到只包含一個元素,就檢查中間的元素
        guess = list[mid] 
        if guess == item:#找到了元素
            return mid
        if guess > item:#猜的數字大了
            high = mid - 1 #向下放縮範圍,因爲索引從零開始,如果不-1就會產生死循環
        else:#猜的數字小了
            low = mid + 1#向上放縮範圍
    return None#沒有指定的元素

if __name__ == '__main__':
    my_list = [1, 3, 5, 7, 9]
    print (binary_search(my_list, 3)) # => 1
    print (binary_search(my_list, 9)) # => 4
    print (binary_search(my_list, -1)) # => None


簡單查找和二分查找的對比

在這裏插入圖片描述

二分法缺點

  • 必須有序,我們很難保證我們的數組都是有序的。
  • 它必須是數組,數組讀取效率是O(1),可是它的插入和刪除某個元素的效率卻是O(n)。

參考書籍《圖解算法》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章