二分查找問題的簡單形式是:直接查找元素值等於指定值的元素。
然而,實際應用中,會有各種各樣的變形問題:
- 存在重複元素值,查找第一個值等於指定值的元素;
- 存在重複元素值,查找最後一個值等於指定值的元素;
- 查找第一個大於等於指定值的元素;
- 查找最後一個小於等於指定值的元素;
對於變形的二分查找問題,基本思路與簡單情況類似,所不同的是需要考慮更復雜的邊界情況的處理。
變形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