二分的思想非常容易理解,但是在代码实现这块二分的边界和判断的条件总是让人迷迷糊糊,分不清什么时候该-1,什么时候不能-1。故自己实现一下C++中的二分搜索函数来巩固,希望自己以后能通过推导出边界如何处理,而不是记忆模板。
在二分中一般有两种基本的实现方法,一种为左闭右闭(也是我使用的)另一种为左闭右开区间。
lower_bount:如果a[mid] == x时 先排除mid以及mid右边,在最后一次循环时(left == right),会触发上面的if语句 使得left为第一个大于等于x的下标。
upper_bount:如果a[mid] == x时 先排除mid以及mid左边,在最后一次循环时(left == right),会触发下面的else语句 使得right左移 同时left为第一个大于x的下标。
上面这两句是二分中模糊不清的地方,也就是最后的判断条件,到底是否需要 r = mid - 1,什么时候需要、什么时候不需要需要好好考虑清楚情况。
实现:
#include <iostream>
using namespace std;
// a 数组中不能含有重复元素,返回 x 的下标
int search_fun(int *a, int n, int x)
{
int left = 0, right = n - 1, mid;// 定义right = n-1及[0, n-1]
while (left <= right) // left == right时 依然有效
{
mid = (left + right) / 2;
if (a[mid] < x)
left = mid + 1;
else if(a[mid] > x)
right = mid - 1;
else
return mid;
}
return -1;
}
// 返回第一个大于等于x的下标
int lower_fun(int *a, int n, int x)
{
int left = 0, right = n - 1, mid;// 定义right = n-1及[0, n-1]
while (left <= right)// left == right时 依然有效
{
mid = (left + right) / 2;
if (a[mid] < x)
left = mid + 1;
else
right = mid - 1;
// 如果a[mid] == x时 先排除mid以及mid右边,在最后一次循环时(left == right),会触发上面的if语句 使得left为第一个大于等于x的下标
}
return left;
}
// 返回第一个大于x的下标
int upper_fun(int *a, int n, int x)
{
int left = 0, right = n - 1, mid;// 定义right = n-1及[0, n-1]
while (left <= right)// left == right时 依然有效
{
mid = (left + right) / 2;
// 如果a[mid] == x时 先排除mid以及mid左边,在最后一次循环时(left == right),会触发下面的else语句 使得right左移 同时left为第一个大于x的下标
if (a[mid] <= x)
left = mid + 1;
else
right = mid - 1;
}
return left;
}
int main()
{
int a[] = {1, 2, 3, 5, 5, 6, 6, 8, 9, 10};
// search方法中的数组不能出现重复元素(不能查询有重复的元素)
cout << search_fun(a, 10, 8) << endl;
cout << lower_fun(a, 10, 5) << endl;
cout << upper_fun(a, 10, 5) << endl;
system("pause");
return 0;
}
大家可以修改数组成上面的内容,去跟着代码的运行一步步的感受二分的边界和判断的条件,感受过之后以后就再也不会通过记忆模板去写二分啦。