數據結構與算法(十四):二分查找問題之變形

二分查找問題的簡單形式是:直接查找元素值等於指定值的元素。

然而,實際應用中,會有各種各樣的變形問題:

  1. 存在重複元素值,查找第一個值等於指定值的元素;
  2. 存在重複元素值,查找最後一個值等於指定值的元素;
  3. 查找第一個大於等於指定值的元素;
  4. 查找最後一個小於等於指定值的元素;

對於變形的二分查找問題,基本思路與簡單情況類似,所不同的是需要考慮更復雜的邊界情況的處理。

變形1:存在重複元素值,查找第一個值等於指定值的元素

比如下面這樣一個有序數組,其中,a[5],a[6],a[7]的值都等於 8,是重複的數據。我們希望查找第一個等於 8 的數據,也就是下標是 5 的元素。

解題思路:

1、和一般二分查找相同,先設定low/high指針,分別指向下標0和9;

2、求解mid指針的下標:mid=low+((high-low)>>1),

3、根據a[mid]與指定值value的關係,選擇更新high指針或者low指針,直到low>high停止。

先上碼,再解釋:

def bisearch_var1_1(A, value):
    low = 0
    high = len(A)-1
    
    while low <= high:
        mid = low + ((high-low)>>1)
        if A[mid] > value:
            high = mid - 1
        elif A[mid] < value:
            low = mid + 1
        elif A[mid] == value:
            if mid == 0 or A[mid-1] != value:
                return mid
            else:
                high = mid - 1
    
    return -1

def bisearch_var1_2(A, value):
    low = 0
    high = len(A)-1
    
    while low <= high:
        mid = low + ((high-low)>>1)
        if A[mid] >= value:
            high = mid - 1
        elif A[mid] < value:
            low = mid + 1
    
    if low < len(A) and A[low]==value:
        return low
    else:
        return -1

這裏給出了兩種方案:

第一種方案——bisearch_var1_1:將a[mid]與指定值value的關係分爲三個:大於、小於、等於。大於和小於的情況比較清晰,就不多說了。對於等於的情況,需要再作判斷:當mid爲0時說明在A[mid] == value情況下,mid是A中第一個元素,那當然也是以第一個等於value的元素;當A[mid-1] != value時,這個時候說明了mid位置的元素等於value但它前一個元素不等於value,由於A有序,那A[mid-1]一定是小於value的,此時mid也就是滿足要求的第一個值等於value的元素的下標;因此,滿足這兩個條件之一即可返回結果,若都不滿足,則說明了這樣一種情況:A[mid]不是第一個等於value的元素,它前面還有其他元素也等於value,所以應該更新high指針,將搜索範圍向low的方向縮小。

第二種方案——bisearch_var1_2:將a[mid]與指定值value的關係分爲兩個個:大於等於、小於。大於等於的時候,說明當前程序還不能確定當前元素A[mid]是第一個等於value的元素,所以更新high指針;小於的時候,說明還沒找到等於value的元素,所以向high的方向更新low指針。這樣經過while循環之後,low、high指針的情況就變成了這樣幾種情況:A中有值等於value的情況——A[low]==value,high=low-1,A[high]<value;A中沒有值等於value的元素,且A[0]<value<A[n-1]的情況(n爲A的長度)——A[low]>value, high = low-1, A[high]<value;A中沒有值等於value的元素,且value>A[n-1]的情況(n爲A的長度)—— high = n-1, low = n,  A[high]<value; A中沒有值等於value的元素,且value<A[0]的情況(n爲A的長度)—— high = -1, low = 0,  A[low]<value。可見,經過while後,幾種情況都有一個共性:low=high+1。然後,再進行一次判斷:low < len(A)且A[low]==value,說明找到值爲value的元素且爲第一個元素,否則就說明沒找到,直接返回-1 。

針對這兩種方案,簡單測試下:

A = [1,2,3,3,3,4,4,5,6]
print(bisearch_var1_1(A,4)) # result: 5
print(bisearch_var1_2(A,0)) # result: -1

 

變形2:存在重複元素值,查找最後一個值等於指定值的元素

解題思路與變形1相同,只是在邊界條件的判斷上有所不同,對於各個情況的分析也類似變形1。廢話少說,直接上碼:

def bisearch_var2_1(A, value):
    low = 0
    high = len(A)-1
    while low <= high:
        mid = low +((high-low)>>1)
        if A[mid] > value:
            high = mid -1
        elif A[mid] < value:
            low = mid + 1
        elif A[mid] == value:
            if mid == len(A)-1 or A[mid+1] != value:
                return mid
            else:
                low = mid + 1
    return -1

def bisearch_var2_2(A, value):
    low = 0
    high = len(A)-1
    while low <= high:
        mid = low + ((high-low)>>1)
        if A[mid] > value:
            high = mid - 1
        elif A[mid] <= value:
            low = mid + 1
    if high >=0 and A[high]==value:
        return high
    else:
        return -1

A = [1,2,3,3,3,4,4,5,6]
print(bisearch_var2_1(A,3)) # result: 4
print(bisearch_var2_2(A,0)) # result: -1

 

變形3:查找第一個大於等於指定值的元素

前面的兩種情況,都是希望能找到等於指定值的元素,而這裏則希望找到第一個大於指定值的元素,所以在邊界判斷時有所不同,先給出兩種解決方案的代碼:

def bisearch_var3_1(A, value):
    low = 0
    high = len(A)-1
    while low <= high:
        mid = low + ((high-low)>>1)
        if A[mid] >= value:
            if mid == 0 or A[mid-1] < value:
                return mid
            else:
                high = mid - 1
        elif A[mid] < value:
            low = mid + 1
    return -1

def bisearch_var3_2(A, value):
    low = 0
    high = len(A)-1
    while low <= high:
        mid = low + ((high-low)>>1)
        if A[mid] >= value:
            high = mid - 1
        elif A[mid] < value:
            low = mid + 1
    if low < len(A):
        return low
    else:
        return -1

A = [1,2,3,3,3,4,4,5,6]
print(bisearch_var3_1(A,0.5)) # result: 0
print(bisearch_var3_2(A,0.5)) # result: 0

對於第一種方案——bisearch_var3_1:將A[mid]與value值的關係分兩種情況:大於等於、小於。當大於等於時,需要判斷如果A[mid]爲第一個元素(也即mid==0),或者mid的前一個元素A[mid-1]小於value,說明要麼這是第一個元素且大於value滿足條件,要麼不是第一個元素但它前一個元素小於value,也滿足條件,這時就直接返回mid即可;否則就更新high。當小於時,直接更新low即可。

對於第二種方案——bisearch_var3_2:將A[mid]與value值的關係分兩種情況:大於等於、小於,與方案bisearch_var3_1所不同的時,bisearch_var3_2將判斷放在了while之後。經過while循環,同樣滿足一個共同條件:low = high + 1,也分三種情況:value<a[0]、a[0]<value<a[n-1]、a[n-1]<value。前兩種情況都屬於能找到滿足條件的元素,第三種情況則屬於找不到滿足條件的元素,因爲第三種情況下A中最大元素都小於value。所以,只有當low小於n-1時,low的值纔是所求。

 

變形4:查找最後一個小於等於指定值的元素

思路類似於變形3,是其反過來的情況,廢話少說,直接上碼:

def bisearch_var4_1(A, value):
    low = 0
    high = len(A)-1
    while low <= high:
        mid = low + ((high-low)>>1)
        if A[mid] > value:
            high = mid - 1
        elif A[mid] <= value:
            if mid == len(A)-1 or A[mid+1] > value:
                return mid
            else:
                low = mid + 1
    return -1

def bisearch_var4_2(A, value):
    low = 0
    high = len(A)-1
    while low <= high:
        mid = low + ((high-low)>>1)
        if A[mid] > value:
            high = mid - 1
        elif A[mid] <= value:
            low = mid + 1
    if high >= 0:
        return high
    else:
        return -1

A = [1,2,3,3,3,4,4,5,6]
print(bisearch_var4_1(A,3)) # result: 4
print(bisearch_var4_2(A,5.5)) # result: 7

 

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