查找算法、python實現

0.概述

0.1.分類

  • 靜態查找和動態查找:針對查找表而言的。動態表指查找表中有刪除和插入操作的表。
  • 無序查找和有序查找:
    無序查找:被查找數列有序無序均可;
    有序查找:被查找數列必須爲有序數列。

0.2.平均查找長度

Average Search Length,ASL
需和指定key進行比較的關鍵字的個數的期望值,稱爲查找算法在查找成功時的平均查找長度。

1. 順序查找、線性查找

說明: 順序查找適合於存儲結構爲順序存儲或鏈接存儲的線性表

基本思想: 屬於無序查找算法。

  1. 從線形表的一端開始,順序掃描。
  2. 兩兩比較,若相等則表示查找成功。
  3. 若掃描結束,仍沒有找到,表示查找失敗。

時間複雜度:O(n)

def sequential_search(lis, key):
    length = len(lis)
    for i in range(length):
        if lis[i] == key:
            return i
    else: # 注意在for外面
        return False

if __name__ == '__main__':
    LIST = [1, 5, 8, 123, 22, 54, 7, 99, 300, 222]
    result = sequential_search(LIST, 123)

2. 二分查找

說明: 元素必須是有序的,如果是無序的則要先進行排序操作。

基本思想: 屬於有序查找算法。

  1. 從數組的中間元素開始,如果中間元素正好是要查找的元素,則查找過程結束。
  2. 如果要查找的元素大於/小於中間元素,則在數組大於/小於中間元素的那一半中查找。而且跟開始一樣從中間元素開始比較。
  3. 如果在某一步驟數組爲空,則代表找不到。

算法步驟
Input:有序數組A[1…n],查找值T
Output:m

  1. L=0,R=n1L=0,R=n-1
  2. 如果L>RL>R,則搜索失敗
  3. 令中間元素索引m=L+R2m=⌊\frac{L+R}2⌋
  4. 如果A[m]<TA[m]<T,令L=m+1L=m+1,返回步驟2
  5. 如果A[m]>TA[m]>T,令R=m1R=m-1,返回步驟2

複雜度分析:
時間複雜度:O(logn),每次把搜索區域減少一半
空間複雜度:O(1)

def binary_search(nums, key):
    l, r = 0, len(nums)-1
    
    while l <= r:
        m = (l + r) // 2
        
        if nums[m] == key:
            return m
        elif nums[m] > key: # 在左半邊
            r = m - 1
        else:
            l = m + 1 # 在右半邊
            
    return False
 
if __name__ == '__main__':
    nums = [1, 5, 7, 8, 22, 54, 99, 123, 200, 222, 444]
    result = binary_search(nums, 99) # return 6
def binary_search(nums, key, l, r):
    if l > r: return False
    
    m = (l + r) // 2
    if nums[m] == key:
        return m
    elif nums[m] > key: # 在左半邊
        return binary_search(nums, key, l, m-1)
    else: # 在右半邊
        return binary_search(nums, key, m+1, r)

if __name__ == '__main__':
    nums = [1, 5, 7, 8, 22, 54, 99, 123, 200, 222, 444]
    result = binary_search(nums, 99, 0, len(nums)-1) # return 6
    print(result)

3. 插值查找

與二分查找的區別:
在這裏插入圖片描述
因此,代碼和二分查找僅有m賦值的區別。

def insert_search(nums, key, l, r):
    if l > r: return False
    
    m = l + int(((key - nums[l])/(nums[r] - nums[l]))*(r - l)) #二分查找爲(l + r) // 2

    if nums[m] == key:
        return m
    elif nums[m] > key: # 在左半邊
        return binary_search(nums, key, l, m-1)
    else: # 在右半邊
        return binary_search(nums, key, m+1, r)

if __name__ == '__main__':
    nums = [1, 5, 7, 8, 22, 54, 99, 123, 200, 222, 444]
    result = insert_search(nums, 99, 0, len(nums)-1) # return 6
    print(result)

emmm我寫代碼發現,兩個嚴重的問題:

  1. 在nums很短、key很大時,很可能會報錯!如下所示:
if __name__ == '__main__':
    nums = [1, 5, 7]
    result = insert_search(nums, 99, 0, len(nums)-1)
    # m = 32, nums[32]
    # IndexError: list index out of range
  1. 博主說在【m=…】後,加入如下判斷,可以避免1中問題。
m = l + int(((key - nums[l])/(nums[r] - nums[l]))*(r - l))
if(mid < low or mid > high): return False

然而,只是錯覺,如下所示:

if __name__ == '__main__':
    nums = [1, 5, 7,999,7]
    result = insert_search(nums, 999, 0, len(nums)-1) # return False
    # m = 665, l = 0, r = 4
    # 進入if,return False
  1. m的計算容易出現“除數是0”的情況。
if __name__ == '__main__':
    nums = [1, 5, 7,999,7,1]
    result = insert_search(nums, 999, 0, len(nums)-1)
    # ZeroDivisionError

綜上:
 對於表長較大,而關鍵字分佈又比較均勻的查找表來說,插值查找算法的平均性能比二分查找要好的多。
 反之,數組中如果分佈非常不均勻,那麼插值查找未必是很合適的選擇。
 尤其是,nums短,要查找的數很大時,不能使用!

複雜度分析:
時間複雜性:如果元素均勻分佈,則O(log(logn)),在最壞的情況下可能需要 O(n)。
空間複雜度:O(1)。

4. 斐波那契查找

留坑!一天一個吧!

5. 樹表查找

6. 分塊查找

7. 哈希查找

參考

https://www.cnblogs.com/lsqin/p/9342929.html
https://www.cnblogs.com/yw09041432/p/5908444.html
https://blog.csdn.net/xiaodongdonglht/article/details/95044327

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