二分搜索是一種時間復雜為log2n的算法,可以用於單調函數求根和單調序列查詢的有效算法,即使數列長度高達10^9 也只需二分31次,查詢速度接近常數,同時二分思想是一種很基礎很重要的思想希望同學們都能掌握;
單調數列(單調遞增)通用二分模板
簡潔版
完全版
當start_index =0,end_index=len-1時,功能和簡潔版一樣;
1.
為啥不能寫下面這句
if(mid==n)
return mid
因為有時候會有
這樣的一種數列
1 2 2 2 2 3
這個是非嚴格單調序列,如果叫你尋找2第一次出現的位置顯然會出現錯誤
所以上面那個寫法是不行的;
2.
為啥不寫left=mid+1和right=mid-1
因為有些情況下會不能用
比如在數列
a[]={0 1 3 4,5}
找大於2的第一個位置
當你right=4,left=0;
則mid=2; A[mid]>2 所以 若r=mid-1=1你就錯過去了,除非你用if判斷一下,但那樣就太冗長難看了;
3.
為啥不能寫
while(left!=right)
因為上面的原因我們沒用 left=mid+1和right=mid-1
而是left=mid,right=mid
若a[]={0,1,3,4};
則找2的位置時會死循環
因為 當left=1,right=2時
mid=(left+right)/2=1;
而a[mid]<2;
所以left=mid=1;
結果left還是等於1,right還是等於2
永遠死循環
現在來解釋一下上面代碼的原理:
我們讓left代表小於等於 x的一方
right代表大於x的一方
我們每次判斷mid是屬於left還是屬於right來使區間縮小一半;
當left與right相遇時,即left+1=right時 分界線就在left和right中間
則從left開始往前小於等於x,從right開始往后大於x
同理其實我們也可以讓
if(f(mid)<x)
left=mid;
else
right=mid;
這時
left代表小於x的一方
right代表大於等於x的一方
當left與right相遇時;
則從left開始往前小於x,從right開始往后大於等於x
即如圖所示
最后我們來教點小技巧
如果我把前一種記做find_upper_bound()
如果我把后一種記做find_lower_bound()
顯然圖觀察可得 兩種的二分結果分別是
x的下標上界和下標下界(包含x)
他們相減就是x的個數
顯然若find_upper_bound(x)==find_lower_bound(x);那么就不存在x
但在使用時不用寫兩種二分,其實由觀察圖可得
find_upper_bound(x-1)=find_lower_bound(x);
或者find_upper_bound(x)=find_lower_bound(x+1);
所以其實寫一種就行了;
這里給出對照表(前提是整數遞增序列才成立,如果是浮點數或則遞減序列時都不成立)
查詢目標 find_upper_bound的用法 find_lower_bound的用法
大於x的第一個位置 find_upper_bound(x) find_lower_bound(x+1)
x出現的第一個位置 find_upper_bound(x-1) find_lower_bound(x)
小於x的第一個位置 find_upper_bound(x-1)-1 find_lower_bound(x)-1
x出現的最后位置 find_upper_bound(x)-1 find_lower_bound(x+1)-1
習題
1.http://210.34.193.66:8080/vj/Problem.jsp?pid=2145
2.http://210.34.193.66:8080/vj/Problem.jsp?pid=1923(這題注意要先用快速排序再二分)
這里是習題1的標程