有一位长者说过: 手写二分是基本的素养。 还有一位长者说过: 写二分要从娃娃抓起。
如果想快速手写二分,首先要明确搜索区间的表示。 有的人喜欢用[]
, 我是[)
即前闭后开区间的支持者。要避免在使用中出错,应该固定使用一种区间表示。
版本1
按照定义直接来的二分查找,就是当查找中点定位到要查找的值时直接返回的查找。 这种查找版本实际上是一个“三分”查找,因为每一轮运行都把区间[lo, hi)
分为了三部分[lo, mi)
, [mi]
, [mi+1, hi)
为了简便,这里用vector<int>
做例子。
int binSearch(vector &A, int tar, int lo, int hi) { while(lo < hi) { int mi = lo + (hi - lo >> 1); if (tar < A[mi]) hi = mi; else if (A[mi] < tar) lo = mi + 1; else return mi; } return -1;}
版本2
二分查找有时候有一些特殊的要求, 比如我们要讨论的要求返回数组中第一个等于tar
的元素。 我们就需要对版本1
做一点修改。要注意,我们使用使用[)
前闭后开的区间表示方式。
这个版本的思想实际上是让[lo, hi)
中的lo
在循环中始终指向第一个大于等于tar
的元素。
int binSearch(vector &A, int tar, int lo, int hi) { while (lo < hi) { int mi = lo + (hi - lo >> 1); if (A[mi] < tar) lo = mi + 1; else hi = mi; } return lo;}
这个版本不像版本1
一样在查找失败的时候可以返回-1
, 所以调用这个函数需要自己做好后续处理。
版本3
接着上面的版本2
, 如果我们想要找到数组中最后一个等于tar
的元素呢? 之前我们是在lo
指向的元素身上做文章,这次我们就在hi
指向的元素身上做文章。 让hi
一直指向第一个大于tar
的元素。
注意: 不要改变[)
的区间表示。
int binSearch(vector & A, int tar, int lo, int hi) { while (lo < hi) { int mi = lo + (hi - lo >> 1); if (A[mi] > tar) hi = mi; else lo = mi + 1; } return hi - 1;}
同样, 这个版本不像版本1
一样在查找失败的时候可以返回-1
, 所以调用这个函数需要自己做好后续处理。
通过 版本2
和 版本3
就可以获得一个目标值的闭区间。