在序列中使用二分查找,可以在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)