在序列中使用二分查找,可以在O(logn) 的時間內查找到需要的元素索引,二分查找的原理很容易理解,但是在代碼中有很多細節需要注意,在決定認真思考總結下二分查找之前,
一般分爲兩種情況
- 查找大於等於 val 的第一個元素索引
std::lower_bound
- 查找大於 val 的第一個元素索引
std::upper_bound
公共代碼
int binaryFind(vector<int> &vi,int val)
{
int left = -1,right = vi.size();
while(right - left > 1)
{
int mid = (left + right) >> 1;
//這裏一般沒有問題
if(vi[mid] > val) right = mid;
else if(vi[mid] < val) left = mid;
//兩種情況的區別
else if(vi[mid] == val)
{
//如何選擇
? = mid;
}
}
//到底返回哪個下標
return ?;
}
查找大於等於 val 的第一個元素索引
... 4 5 5 5 5 5 6 7 8 ...
假如 val = 5,而此時 mid 指向第一個 5
-
令 left = mid
下一次 mid 就會指向後面的數 (這裏必然會繼續循環的原因是right至少在 6 的索引處),顯然查找區間在向右移動,而我們期望返回的是第一個 5 的下標,已經出現錯誤 -
令 right = mid
下一次 mid 就會指向 5 之前的數(不管是否繼續循環,left 必然在 5 之前,就算相鄰 mid 也會在 5 之前),同時這也是 right 最終的位置,因爲以後的 mid 肯定小於 val,那麼最終情況就是 left 指向 4,right 指向 5 跳出循環
假如 val = 5,而此時 mid 指向最後一個 5
- 令 left = mid
已經不可能返回期望的結果了 - 令 right = mid
和前面的結論一樣,left 一定在 5 之前,查找區間繼續向左移動,直到 right 指向第一個 5
結論非常清楚
if(vi[mid] == val) right = mid;
return right;
查找大於 val 的第一個元素索引
... 4 5 5 5 5 5 6 7 8 ...
假如 val = 5,而此時 mid 指向第一個 5
假如 val = 5,而此時 mid 指向最後一個 5
從前面的模擬可以得出,如果令 right = mid,那麼最終 left 指向 4,right 指向第一個 5,不符合此時期望的結果,所以 left = mid
簡單模擬下 left = mid 的情況可知,最後一個 5 會成爲 left 最終指向的位置,不會繼續向右,所以最終跳出循環時,left 指向最後一個 5,right 指向 6
結論也就非常清楚了
if(vi[mid] == val) left = mid;
return right;
總結
查找大於等於 val 的第一個元素索引
int binaryFind(vector<int> &vi,int val)
{
int left = -1,right = vi.size();
while(right - left > 1)
{
int mid = (left + right) >> 1;
if(vi[mid] > val) right = mid;
else if(vi[mid] < val) left = mid;
else if(vi[mid] == val) right = mid;
}
return left;
}
或者使用 std::lower_bound(vi.begin(),vi.end(),val)
查找大於 val 的第一個元素索引
int binaryFind(vector<int> &vi,int val)
{
int left = -1,right = vi.size();
while(right - left > 1)
{
int mid = (left + right) >> 1;
if(vi[mid] > val) right = mid;
else if(vi[mid] < val) left = mid;
else if(vi[mid] == val) left = mid;
}
return left;
}
或者使用 std::upper_bound(vi.begin(),vi.end(),val)