二分查詢,它的時間複雜度是 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.查詢最後乙個等...