二分查詢及其變體(Python)

2021-09-24 06:16:55 字數 4059 閱讀 7167

二分查詢,它的時間複雜度是 o(logn)。  其核心思想有點類似分治思想。

即每次都通過跟區間中的中間元素對比,將待查詢的區間縮小為一半,直到找到要查詢的元素,或者區間被縮小為 0。

但是二分查詢的**實現比較容易寫錯。你需要著重掌握它的三個容易出錯的地方:迴圈退出條件、mid 的取值,low 和 high 的更新。  

二分查詢雖然效能比較優秀,但應用場景也比較有限。底層必須依賴陣列,並且還要求資料是有序的。對於較小規模的資料查詢,我們直接使用順序遍歷就可以了,二分查詢的優勢並不明顯。二分查詢更適合處理靜態資料,也就是沒有頻繁的資料插入、刪除操作。

class solution:

def bsearch(self, nums, target):

"""binary search of a target in a sorted array

without duplicates. if such a target does not exist,

return -1, othewise, return its index.

"""left, right = 0, len(nums)-1

while left <= right:

mid = left + (right - left) // 2

if nums[mid] == target:

return mid

elif nums[mid] < target:

left = mid + 1

else:

right = mid - 1

return -1

s = solution()

a = [1,3,5,6,7,9,11]

print(s.bsearch(a, 7))

凡是用二分查詢能解決的,絕大部分我們更傾向於用雜湊表或者二叉查詢樹。即便是二分查詢在記憶體使用上更節省,但是畢竟記憶體如此緊缺的情況並不多。

那二分查詢真的沒什麼用處了嗎?實際上,求「值等於給定值」的二分查詢確實不怎麼會被用到二分查詢更適合用在「近似」查詢問題,在這類問題上,二分查詢的優勢更加明顯。比如這幾種變體問題,用其他資料結構,比如雜湊表、二叉樹,就比較難實現了。

變體的二分查詢演算法寫起來非常燒腦,很容易因為細節處理不好而產生 bug,容易出錯的細節有:終止條件、區間上下界更新方法、返回值選擇。

from typing import list

def bsearch_left(a: list[int], target: int) -> int:

"""binary search of the index of the first element

變體一:查詢第乙個值等於給定值的元素

"""low, high = 0, len(a) - 1

while low <= high:

mid = low + (high - low) // 2

if a[mid] < target:

low = mid + 1

elif a[mid] > target:

high = mid - 1

else: # nums[mid]=target

if mid == 0 or a[mid-1] != target: # 如果 mid 等於 0,那這個元素已經是陣列的第乙個元素,那它肯定是要找的;

# 如果 mid 不等於 0,但 a[mid] 的前乙個元素 a[mid-1] 不等於 target,那也說明 a[mid] 就是我們要找的第乙個值等於給定值的元素

return mid

else:

high = mid - 1 # 繼續找,要找的元素出現在 [low, mid-1] 之間

return -1

def bsearch_right(a: list[int], target: int) -> int:

"""binary search of the index of the last element

變體二:查詢最後乙個值等於給定值的元素

"""low, high = 0, len(a)-1

while low <= high:

mid = low + (high - low)//2

if a[mid] < target:

low = mid + 1

elif a[mid] > target:

high = mid - 1

else:

if mid == len(a)-1 or a[mid+1] != target:

return mid

else: # 繼續在[mid+1, high]之間找

low = mid + 1

return -1

if __name__ == "__main__":

a = [1, 1, 2, 3, 4, 6, 7, 7, 7, 7, 10, 22]

print('查詢第乙個值等於給定值的元素 index')

print(bsearch_left(a, 1))

print(bsearch_left(a, 7))

print('查詢最後乙個值等於給定值的元素 index')

print(bsearch_right(a, 1))

print(bsearch_right(a, 7))

from typing import list

def bsearch_left_not_less(a: list[int], target: int) -> int:

"""binary search of the index of the first element

變體三:查詢第乙個大於等於給定值的元素

"""low, high = 0, len(a) - 1

while low <= high:

mid = low + (high - low) // 2

# 滿足,繼續檢查是不是第乙個

if a[mid] >= target:

if mid == 0 or a[mid-1] < target: # 是乙個

return mid

else: # 不是第乙個,則縮小查詢區間

high = mid - 1

# 不滿足

else:

low = mid + 1

return -1

def bsearch_right_not_greater(a: list[int], target: int) -> int:

"""binary search of the index of the last element

變體四:查詢最後乙個小於等於給定值的元素

"""low, high = 0, len(a) - 1

while low <= high:

mid = low + (high - low) // 2

if a[mid] <= target:

if mid == len(a)-1 or a[mid+1] > target:

return mid

else:

low = mid + 1

else:

high = mid - 1

return -1

if __name__ == "__main__":

a = [1, 1, 2, 3, 4, 6, 7, 7, 7, 7, 10, 22]

print('找第乙個大於等於給定值的元素 index')

print(bsearch_left_not_less(a, 6))

print(bsearch_left_not_less(a, 8))

print('查詢最後乙個小於等於給定值的元素 index')

print(bsearch_right_not_greater(a, 6))

print(bsearch_right_not_greater(a, 8))

深入分析二分查詢及其變體

一般的二分查詢 如下 int search int a,int n,int target return 1 上面的二分查詢非常的樸實,上述二分查詢的作用當然就是 找到陣列a中等於target的元素。但是這個查詢元素隱含了乙個條件 這個陣列的元素是不包含重複元素的。這個限制可以說是非常的大。我們來看一...

二分查詢以及變體 JS

常規二分法 function twopart nums,key return 1 小於 小於等於的最後乙個數 function twopartsmall nums,key return hi 大於 大於等於的第乙個數 function twopartbig nums,key return lo 減而...

二分法及其變體問題

1.尋找乙個元素在陣列中的位置 二分查詢 int midfind1 int lo,int hi,int tar return 1 2.查詢第乙個等於給定值的元素 查詢第乙個等於給定值的元素 int firsteqpos int lo,int hi,int tar return 1 3.查詢最後乙個等...