二分查找

定義

二分查找:
思路很簡單,細節是魔鬼。
基本框架:

def find(nums,target):
    left = 0
    right = ***
    while left ** right:
        mid = (left + right) // 2
        if nums[mid] == target:
            ***
        elif nums[mid] > target:
            right = ***
        elif nums[mid] < target:
            left = ***
    return ***

以上框架中的**代表的內容就是細節,需要注意

應用

尋找一個數

def find(nums,target):
    left = 0
    right = len(nums) - 1   # 注意1
    while left <= right:    # 注意2
        mid = (left + right) // 2
        if nums[mid] == target:
            return mid
        elif nums[mid] > target:
            right = mid - 1   # 注意3
        elif nums[mid] < target:
            left = mid + 1   # 注意4

注意1:
right是數組長度 - 1,用的是左閉右閉的區間。
注意2:
用的是left <= right,即終止條件是left = right + 1。因爲除了找到後結束搜索,還有一種情況是搜索區間爲空,當區間爲[right+1,right]時肯定爲空了;但是如果用的是left < right,即終止條件是left = right ,當區間爲[right,right]時區間並沒有空,還有一個索引沒有判斷。
所以如果用left < right,則是以下代碼:
在這裏插入圖片描述
注意3和注意4:
這個比較好理解。因爲mid這個位置已經搜索過,所以沒必要再搜索。

該算法是有缺陷的,對於所求目標在數組中重複的這種情況,比如[1,2,2,2,3],此時用這個算法返回的索引是2,但是有時候我們想找到target的左側邊界,比如索引1或者是target的右側邊界,比如索引3。

尋找左側邊界

如果要找一個有序數組中小於某個數的個數有多少個,則用以下代碼:

def find(nums,target):
    left = 0
    right = len(nums)
    while left < right:
        mid = (left + right) // 2
        if nums[mid] == target:
            right = mid
        elif nums[mid] > target:
            right = mid
        elif nums[mid] < target:
            left = mid + 1
    return left  # 返回right也可以,因爲left == right

所以在以上代碼上進行改進就能得到某個數在數組中的左側邊界:

def find(nums,target):
    left = 0
    right = len(nums)         # 注意1
    while left < right:      # 注意2
        mid = (left + right) // 2
        if nums[mid] == target:
            right = mid      # 注意3
        elif nums[mid] > target:
            right = mid      # 注意4
        elif nums[mid] < target:
            left = mid + 1   # 注意5
    if left == len(nums):
        return -1
    return left if nums[left] == target else -1 

注意1:
right是數組長度 ,用的是左閉右開的區間。
注意2:
用的是left < right,即終止條件是left = right 。因爲除了找到後結束搜索,還有一種情況是搜索區間爲空,當區間爲[right,right)時肯定已經爲空了。
注意3:
當找到目標時並不是返回已找到,而是要繼續在左側尋找,看是否還有,因爲要得到左側邊界。要切記是左閉右開。
注意4和注意5:
左閉右開,所以左面需要加一,右面不需要,因爲取不上。

尋找右側邊界

def find1(nums,target):
    left = 0
    right = len(nums)         
    while left < right:      
        mid = (left + right) // 2
        if nums[mid] == target:
            left = mid + 1         # 注意1
        elif nums[mid] > target:
            right = mid      
        elif nums[mid] < target:
            left = mid + 1 
    if left == 0:
        return -1
    return (left -1) if nums[left-1] == target else -1   # 注意2

注意1:
當找到目標時並不是返回已找到,而是要繼續在右側尋找,看是否還有,因爲要得到右側邊界。要切記是左閉右開。
注意2
return left - 1 中,寫成return right - 1 也可以,因爲left = right,爲什麼這樣就能得到右側邊界呢?因爲我們對left的更新是left = mid + 1,所以while循環結束時num[left]一定不等於target了。而 num[left-1]有可能是target。

總結

  1. 最基本的二分查找算法:
    初始化爲 right = len(nums) - 1,所以決定了搜索區間是 [left, right],所以決定了 while (left <= right),同時也決定了 left = mid+1 和 right = mid-1,因爲我們只需找到一個 target 的索引即可
    所以當 nums[mid] == target 時可以立即返回。
  2. 尋找左側邊界的二分查找:
    初始化 right = len(nums),所以決定了搜索區間是 [left, right),所以決定了 while (left < right)
    同時也決定了 left = mid+1 和 right = mid ,因爲我們需找到 target 的最左側索引,所以當 nums[mid] == target 時不要立即返回,而要收緊右側邊界以鎖定左側邊界。
  3. 尋找右側邊界的二分查找
    因爲我們初始化 right = len(nums),所以決定了我們的搜索區間是 [left, right),所以決定了 while (left < right),同時也決定了 left = mid+1 和 right = mid,因爲我們需找到 target 的最右側索引,所以當 nums[mid] == target 時不要立即返回而要收緊左側邊界以鎖定右側邊界。又因爲收緊左側邊界時必須 left = mid + 1,所以最後無論返回 left 還是 right,必須減一。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章