Binary Search
二分的原理是利用区间内值有序的特点, 不断让可行区间减半, 最终可行区间长度减到1得到答案
要保证二分能得到正确答案并且不会死循环, 要保证两个条件:
1. 解一直在候选区间里
2. 每次判断后可行区间都会缩小(特别是左右端点相距为1的时候)
所以写二分的时候,一定要测试只有两个点时,不同情况下区间是否会缩减成一个(那一个就是解),否则算法可能陷入死循环。
除了二分寻找某个元素,upper_bound 和 lower_bound 都可以借助二分来做。
upper_bound返回第一个大于的元素的下标;
lower_bound返回第一个大于等于元素的下标。
闭区间 [low, high]
lower_bound:
初始参数为 low=0, high=n-1
搜索区间为[low, high], 返回值候选区间[low, high+1]
A[mid]<key时,搜索区间更新为[mid+1,high], 返回值候选区间[mid+1, high+1]
A[mid]>=key时,搜索区间更新为[low,mid-1], 返回值候选区间[low, mid]
while (low<=high) 需要包含等于号,这是因为如果while (low<high),终止时low=high,返回值候选区间[low, high+1]有两个值。
while (low<=high) 终止时low>high, 即low>=high+1。又由于low<=high+1,所以low=high+1。此时候选区间只有一个元素low,low就是解。
换一种思路,由于返回值候选区间[low, high+1],只有当low>=high+1时才有唯一解。因此not(low>=high+1),即low<=high时都要循环。
int bsearch(int *A, int low, int high, int key){ //[low,high] int mid; while (low<=high){ mid = (low+high)/2; if (A[mid]==key) return mid; else if (A[mid]<key) low = mid+1; else high = mid-1; } return -1; } int lower_bound(int *A, int low, int high, int key){ //[low,high] int mid; while (low<=high){ mid = (low+high)/2; if (A[mid]<key) low = mid+1; else high = mid-1; } return low; } int upper_bound(int *A, int low, int high, int key){ //[low,high] int mid; while (low<=high){ mid = (low+high)/2; if (A[mid]<key) low = mid+1; else if (A[mid]==key) low = mid+1; else high = mid-1; } return low; }
有另一种理解方式,while (low<=high) 终止条件是low>high,此时搜索区间为空。满足终止条件以后,low就是答案。
这是因为这个方法就是维护low的过程,只有A[mid]<key时,low才会前进,逼近最终的答案。而high是单纯缩小数组的空间。
左闭右开区间 [low, high)
lower_bound:
初始参数为 low=0, high=n
搜索区间为[low, high), 返回值候选区间[low, high]
A[mid]<key时,搜索区间更新为[mid+1,high), 返回值候选区间[mid+1,high]
A[mid]>=key时,搜索区间更新为[low,mid), 返回值候选区间[low, mid]
while (low<high) 终止时low>=high,又由于low<=high,所以low=high。候选区间只有一个元素low即所求。
换一种思路,由于返回值候选区间[low, high],只有当low>=high时才有唯一解。因此not(low>=high),即low<high时都要循环。
// 普通二分查找 int bsearch(int *A, int low, int high, int key){ //[low, high) int mid; while (low<high){ mid = (low+high)/2; if (A[mid]==key) return mid; else if (A[mid]<key) low = mid+1; else high = mid; } return -1; } // 查找下界 // 当 x 存在时返回它出现的第一个位置,否则返回这样一个下标 i; // 在此处插入 val 后序列仍然有序 int lower_bound(int *A, int low, int high, int key){ //[low, high) int mid; while (low<high){ mid = (low+high)/2; if (A[mid]<key) low = mid+1; else high = mid; } return low; } // 查找上界 // 当 x 存在时返回它出现的最后一个位置后面一个位置,否则返回这样一个下标 i; // 在此处插入 val 后序列仍然有序 int upper_bound(int *A, int low, int high, int key){ //[low, high) int mid; while (low<high){ mid = (low+high)/2; if (A[mid]<key) low = mid+1; else if (A[mid]==key) low = mid+1; else high = mid; } return low; }
可以看出,lower_bound和upper_bound只有在对 A[mid]==key时有所不同,其余都是相同的。自己写的时候也可以三种情况分别考虑一下,合不合并都无所谓。
设lower_bound和upper_bound的返回值分别为L和R,则key出现的子序列为[L,R)。这个结论在key不存在时也成立,此时L=R,区间为空。
总结:
要准确实现二分查找,要把握下面几个要点:
high=n-1 => while (low<=high) => high=mid-1;
high=n => while (low<high) => high=mid;
换言之,算法所操作的区间,是左闭右开区间,还是左闭右闭区间,这个区间,需要在循环初始化。且在循环体终止的判断中,以及每次修改low, high区间值这三个地方保持一致,否则就可能出错。
这篇博客总结的也很不错,可做参考 http://www.cnblogs.com/grandyang/p/6854825.html

浙公网安备 33010602011771号