0.前言
二分思想十分容易理解,但是二分法边界处理问题大多数人都是记忆模板,忘记模板后处理边界就一团乱。之前我也凭借记忆书写,没彻底理清二分的边界关系,这次就此机会理清。
记得某本书里有句经典发言:
90%的程序员不能完全正确的写一个二分查找算法。
1.故事分享📖
有一天小明到图书馆借了 N 本书,出图书馆的时候,警报响了,于是保安把小明拦下,要检查一下哪本书没有登记出借。小明正准备把每一本书在报警器下过一下,以找出引发警报的书,但是保安露出不屑的眼神:你连二分查找都不会吗?于是保安把书分成两堆,让第一堆过一下报警器,报警器响;于是再把这堆书分成两堆…… 最终,检测了 logN 次之后,保安成功的找到了那本引起警报的书,露出了得意和嘲讽的笑容。于是小明背着剩下的书走了。 从此,图书馆丢了 N - 1 本书。
保安怎么知道只有一本书📖没有登记出借,万一全部都没有登记呢?
这个故事其实说出了二分查找需要的条件:
- 用于查找的内容逻辑上来说是需要有序的
- 查找的数量只能是一个,而不是多个
二分法最重要的两个点:
- while循环中 left 和 right 的关系,到底是 left <= right 还是 left < right
- 迭代过程中 middle 和 right 的关系,到底是 right = middle - 1 还是 right = middle
2.左闭右闭
-
mid 是比较过的,因此 begin 是到 mid+1 的位置,end是到 mid-1 的位置
-
循环条件要使用
while (begin <= end)
,因为当begin = end
这种情况发生的时候,得到的结果是有意义的
//左闭右闭的写法
int BinarySearch(int *a, int n, int x)
{
assert(a);
int begin = 0;
int end = n-1;
while (begin <= end)//这里用的是左闭右闭
{
int mid = begin + ((end - begin) >> 1);//避免相加可能溢出
if (a[mid] < x)
begin = mid + 1;
else if (a[mid] > x)
end = mid - 1;
else
return mid;
}
return -1;
}
3.左闭右开
- 当 x > mid 时,begin 要落到 mid+1 的位置,mid 的位置已经是查验过的了
- 当 x < mid 时,end 要落到 mid 的位置,因为是左闭右开,实际上mid的位置是无法访问的
int BinarySearch(int *a, int n, int x)//对已经有序的数组进行搜索
{
assert(a);
int begin = 0;
int end = n;
while (begin < end)//这里用的是左闭右开 [begin,end)
{
int mid = begin + ((end - begin) >> 1);
if (a[mid] < x)
begin = mid + 1;//如果给的是 mid 有可能会死循环
else if (a[mid] > x)
end = mid;//如果到了mid-1 因为是开区间,其实找不到mid-1 的
else
return mid;
}
return -1;
}
4.错误示范
1.找不到
int BinarySearch(int *a, int n, int x)
{
assert(a);
int begin = 0;
int end = n;
while (begin < end)
{
int mid = begin + ((end - begin) >> 1);
if (a[mid] < x)
begin = mid + 1;
else if (a[mid] > x)
end = mid - 1;
else
return mid;
}
return -1;
}
int main()
{
int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for (int i = 0; i < sizeof(a) / sizeof(int); ++i)
{
printf("%d\n", BinarySearch(a, 10, i));
}
system("pause");
return 0;
}
![image-20220305220428229](https://i-blog.csdnimg.cn/blog_migrate/ccafd3bd8f369b5ee9053f732546369a.png)
2.死循环
如果 a[mid] < x 时 begin = mid 可能会死循环
begin指向6,end指向7,mid 还是指向6 ==》死循环
5.总结
二分法最重要的两个点,就是循环条件和后续的区间赋值问题
因为两者是相互联系,相互影响的,所以就需要两者统一,如果两者不统一,就会出现问题。所以循环条件和赋值问题必须统一。