1.二分查找的前提:
二分查找属于静态查找表系列算法的有序表查找算法,意思就是二分查找应该使用在有序数列中,数列必须是升序或者降序排列。
2.主流二分查找应用场景:
2.1 二分查找目标值
int find(int *ar, int l, int r, int target) {
while(l <= r) {
int mid = (l + r) / 2;
if(ar[mid] == target)
return mid;
else if(ar[mid] > target)
r = mid - 1;
else
l = mid + 1;
}
return -1;
}
思路:
二分查找的思路大致叙述为是根据中间值的状况,每次把查找区间缩小一半。当查找区间缩小为左边界等于右边界时到达查找最终状态,下一个状态退出查找。
2.2 二分查找第一个比目标值大的值
int find(int *ar, int length, int target) {
int l = 0, r = length - 1, mid;
while(l <= r) {
mid = (l + r) / 2;
if(ar[mid] <= target)
l = mid + 1;
else
r = mid - 1;
}
return l;
}
思路:
基本思路:
二分查找第一个比目标值大的值的思路大致叙述为是根据中间值的状况,每次把查找区间缩小一半。当查找区间缩小为左边界等于右边界时到达查找最终状态,下一个状态退出查找。但是由此无法确定状态退出时到底是左边界为预期结果还是右边界是预期结果。
返回值是左边界还是右边界的确认:
由于二分查找应用在有序数列中,因此除了最终状态,其余所有状态下都是左边界的值小于右边界的值,且目标值介于两者之间(假设在一个升序顺序的数列中)。因此倒数第二个状态一定是区间长度为2,左边界小于右边界,由此推导出最终状态一定是右边界左移一位,此时左边界等于右边界且当前值小于目标值。再下一个状态时,退出查找,一定是左边界右移一位,此时左边界对应的值就是第一个比目标值大的值。
数列中存在多个目标值的解决方案:
假如数列中存在一些数和目标值相等,此时要考虑在这种
ar[mid] = target
情况下到底要让左边界右移还是让右边界左移。在二分查找中,除了最后一次区间,其余区间都应该满足目标值介于左边界对应值和右边界对应值之间。如果在ar[mid] = target
情况下让右边界左移,那么将不能满足二分查找边界要求。因此应该让左边界右移,就是上述代码的ar[mid] <= target
对应的处理方案。
测试:
int ar[12] = {1, 2, 3, 4, 5, 6, 6, 6, 7, 8, 9, 10}, target = 6;
cout << find(ar, 0, 11, 6); // 8
2.3 二分查找第一个比目标值小的值
int find(int *ar, int l, int r, int target) {
while(l <= r) {
int mid = (l + r) / 2;
if(ar[mid] <= target)
r = mid - 1;
else
l = mid + 1;
}
return l;
}
思路:
思路同上
3.归纳二分查找的要求
- 二分查找适用于升序或降序的有序序列。
- 二分查找基本思路是每次查找将查找区间缩小一半,最终状态时左边界等于右边界,下一个状态停止查找。
- 二分查找在查找过程中应该始终保持目标值介于左边界对应值和右边界对应值之间。
- 二分查找目标值时返回中间值mid,二分查找第一个比目标值大/小的值时返回左边界l。