感谢 @TheNew123 指正错误
背景
二分查找(英语:binary search),也称折半搜索(英语:half-interval search)、对数搜索(英语:logarithmic search),是用来在一个 有序数组 中查找某一元素的算法。
原理
定义左变量,右变量,表示查找区间;为数组长度;为要查找数
初值,初值
先将数组位置的数与比较,如果大于,,小于的话,,然后不断查找,直到找到或者小于为止。
算法设计
(1)设置左量和右量和,分别赋值和(数组长度)
int left=1,right=n;
(2)如果查找的范围 不存在,则查找失败,否则进(3)
(3)取中间位置 ,比较要查找的数和:
1. 若 x < a[mid],则right=mid-1;查找在左半区间进行,转(2);
2. 若 x > a[mid],则left=mid+1;查找在右半区间进行,转(2);
3. 若 x = a[mid],则查找成功,返回mid的值,结束;
4.如果left > right,跳出循环。
by 薛定谔的史蒂夫的博客 - 《C++二分查找》
时间复杂度
(1)普通循环查找
最好:
最坏:
(2)二分方法查找
最好(就在中间):
最坏(在两端):
代码实现
没有重复的情况
非递归
int binary_search(int left,int right,int x) {
int ret = -1; //未搜索到数据返回-1
int mid;
while (left <= right) {
mid = (left+right) >>1; //直接平均可能会范围超限,所以用位运算,即除以2的1次方
if (a[mid] < x) left = mid + 1;
else if (a[mid] > x) right = mid - 1;
else{ //不大不小就等于
ret = mid;
break;
}
}
return ret;
}
递归
懒人专用,但注意数据大时栈溢出的问题
int binary_search(int left,int right,int x) {
int mid = (left+right) >>1;
if(x < a[mid]) return binary_search(a,left,mid-1,x);
else if(x > a[mid]) return binary_search(a,mid+1,right,x);
else return mid;
}
有重复的情况
如果有重复值,且需要让我们得到最左值、最右值或全部值,那么前面的代码我们只能找到一个。
我们需要想到一种方法找到最左和最右值。找到全部值其实等价于找到最左值和最右值。
如果直接遍历的话可能使得复杂度退化,是绝对不可取的!
其实我们在上面的问题中只需要将条件改一下,找最左值就变成,想找最右值就把改成。
这样找最左值时我们找到目标值还要向左查找,找最右值时找到目标值要向右查找。不过写代码时还要注意一些细节。
改自 C++二分查找_shldy1999的博客-CSDN博客_二分查找c++
找最左值
//循环
int binary_search(int left,int right,int x) {
int mid;
while (left <= right) {
mid = left + ((right - left) >> 1); //直接平均可能会范围超限,所以用位运算,即除以2的1次方
if (a[mid] < x) left = mid + 1;
else if (a[mid] >= x) right = mid-1; //精髓
}
return a[left]==x? left:-1; //循环结束时实际上是left>right,所以left指的就是最左值
}
//递归错误示范
int binary_search(int left,int right,int x) {
int mid = left + ((right - left) >> 1);
if(x <= a[mid]) return binary_search(left,mid,x);
//同上代码注释
else if(x > a[mid]) return binary_search(mid+1,right,x);
else return mid;
}
但这样可以吗?——不可以!因为如果按照小于等于的逻辑去排,那么永远都递归不完,最终只会
boom:
找最右值
//循环
int binary_search(int left,int right,int x) {
int mid;
while (left <= right) {
mid = (left+right) >>1; //直接平均可能会范围超限,所以用位运算,即除以2的1次方
if (a[mid] <= x) left = mid+1; //精髓
else if (a[mid] > x) right = mid - 1;
}
return a[right]==x? right:-1; //同最左值
}
——————————————————————————————————————————
因为left=mid+1会越过边界值,所以我们采用这种写法,然而这样会出问题。我们在无重复值的代码里提到过,当left=1,right=2,那么mid将等于1,因此比较后如果a(mid) < b那么left必须等于mid+1,否则left,right和mid将不变,进入死循环。在这里也一样适用。因此此种方法不行。
解决的方法可能有很多,其中一种是的是不寻找最右值,改为寻找最右值右边的值,这样就不用担心left=mid+1会越过边界值。代码如下:
以上摘自 C++二分查找_shldy1999的博客-CSDN博客_二分查找c++
//函数内循环部分
while (left!=right) {
mid = left + ((right - left) >> 1);
if (a[mid] > x) right = mid;
else if (a[mid] <= x) left = mid + 1;
else{
ret=mid;
break;
}
}
//需要特判
if(a[left]==x) return left-1;
else return left;
——————————————————————————————————————————
上面这一部分是参考了别人的博客,然而经过实验发现并不准确,其实并不会发生以上情况。(个人实验,仅为参考,有问题可以评论指出)
总结
二分查找是C++算法的重要部分和难点,up主也是查阅的多位dalao的博文才勉强弄懂,希望大家能潜心理解,多找例子做,多看文章……相信所有的困难都将在努力面前灰飞烟灭!
代码如果有问题的话请评论或私信up主,看到及时改正。
感谢 @TheNew123 指正错误
掰掰ヾ(•ω•`)o
本文可以转载,请注明作者,谢谢