二分查找也叫作折半查找。二分查找有两个要求,一个是数列有序,另一个是数列使用顺序存储结构。他的思想很简单,但是在书写过程中如果边界条件无法正确的确定,很容易陷入到循环中无法跳出。
二段性
二段性是集合中的元素有存在分界线,给定条件可以将集合中元素分为两部分,一部分满足条件,一部分不满足条件。
- 分界线之前的第一个元素,也就是最后一个满足条件的值,我们称为ML(Meeting Condition Last)
- 分界线之后的第一个元素,也就是第一个不满足该条件的值,我们称为NB(Not Meeting Condition Begining)
区间问题
此节主要讲二分查找的细节处理,如果要看二分的查找过程不建议看这篇,文中所讲的二分伪代码为:
l = -1,r = N + 1 while l + 1 != r m = l + (r - l >> 1) if judge(m) // 根据题目修改judge l = m else r = m return l/r // 最终返回时根据需求进行返回
对于一个给定的数组 0 1 2 3 ⋯ ( N − 4 ) ( N − 3 ) ( N − 2 ) ( N − 1 ) N 0\ \ 1\ \ 2\ \ 3\ \ \cdots\ \ (N-4)\ \ (N-3)\ \ (N-2)\ \ (N-1)\ \ N 0 1 2 3 ⋯ (N−4) (N−3) (N−2) (N−1) N进行二分查找时,需考虑以下步骤
- 确定左右边界:这里确定左右边界分别为 − 1 -1 −1和 N + 1 N+1 N+1。
面对查找小于 0 0 0的最后一个数时,如果令左边界的值为 0 0 0,那么左边界的初始条件已经不满足问题要求了,因此采用0作为一个通用的二分求解方法的左边界并不是一个合理的方案。
同理,面对查找大于 N N N的第一个数,也不可以令右边界的值为1。
- 获得中点:使用 m = l + ( r − l > > 1 ) m = l + (r - l >> 1) m=l+(r−l>>1)作为获得重点的公式。
采用其他的公式,比如 m = l + r > > 1 m=l+r>>1 m=l+r>>1,也是可以的,但是为了防止 l l l与 r r r相加导致的数值溢出使用上面的公式。
- 收缩区间:使用 l = m l=m l=m和 r = m r=m r=m作为收缩区间的公式。
为了避免写出死循环,必须保证每次进入 w h i l e while while之后,可行区间都在变小,且最终推出循环。在此先引出程序终止的条件为 l + 1 = r l+1=r l+1=r,当面对 l + 1 = r l+1=r l+1=r,此时以满足终止条件所以无论 l l l和 r r r取多少,都会退出循环;当面对 l + 2 = r l+2=r l+2=r,中点为 m = l + 1 m=l+1 m=l+1,此时循环体内的收缩公式会将 m m m赋值给 l l l或者 r r r,再此达到终止条件,退出循环;当面对 l + 3 = r l+3=r l+3=r,按照 l + 2 = r l+2=r l+2=r里的分析,其最终会进入到 l + 2 = r l+2=r l+2=r或者 l + 1 = r l+1=r l+1=r的条件。
- 确定终止条件:这里终止条件为 l + 1 = r l + 1 = r l+1=r,即循环条件为 l + 1 ! = r l + 1\ != r l+1 !=r。
因为的目标时遍历整个数组,因此终止条件要确保所有的元素都可以被包含在内。同上面的收缩区间的公式相配合可以构成一个二分查找。
- l l l和 r r r的指向问题
l l l和 r r r的指向哪个数值,最终还是取决于judge条件。对于我们要查找小于零第一个数,其judge条件为 m < 0 m<0 m<0, l = − 1 l=-1 l=−1, r = 0 r=0 r=0。
对于一个例子 1 , 2 , 3 , 5 , 5 , 5 , 8 , 9 1, 2, 3, 5, 5, 5, 8, 9 1,2,3,5,5,5,8,9:
找到**小于 t t t**的最后一个数:
class Solution { public int binarySearch(int[] arr, int t) { int l = -1, r = arr.length; while (l + 1 != r) { int m = l + (r - l >> 1); if (arr[m] < t) l = m; // judge条件:如果小于t,则让l=m;否则r=m else r = m; } return l; // 返回l } } Solution solution = new Solution(); System.out.println(solution.binarySearch(new int[]{1, 2, 3, 5, 5, 5, 8, 9}, 5)); // 返回2,对应的值为3
找到**小于等于 t t t**的最后一个数:
class Solution { public int binarySearch(int[] arr, int t) { int l = -1, r = arr.length; while (l + 1 != r) { int m = l + (r - l >> 1); if (arr[m] <= t) l = m; // judge条件:如果小于等于t,则让l=m;否则r=m else r = m; } return l; // 返回l } } Solution solution = new Solution(); System.out.println(solution.binarySearch(new int[]{1, 2, 3, 5, 5, 5, 8, 9}, 5)); // 返回5,对应的值为5
找到**大于 t t t**的第一个数:(判断条件等价于找到小于等于 t t t的最后一个数)
class Solution { public int binarySearch(int[] arr, int t) { int l = -1, r = arr.length; while (l + 1 != r) { int m = l + (r - l >> 1); if (arr[m] <= t) l = m; // judge条件:如果小于等于t,则让l=m;否则r=m else r = m; } return r; // 返回r } } Solution solution = new Solution(); System.out.println(solution.binarySearch(new int[]{1, 2, 3, 5, 5, 5, 8, 9}, 5)); // 返回6,对应的值为8
找到大于等于 t t t的第一个数:(判断条件等价于找到小于等于 t t t的最后一个数)
class Solution { public int binarySearch(int[] arr, int t) { int l = -1, r = arr.length; while (l + 1 != r) { int m = l + (r - l >> 1); if (arr[m] < t) l = m; // judge条件:如果小于t,则让l=m;否则r=m else r = m; } return r; // 返回r } } Solution solution = new Solution(); System.out.println(solution.binarySearch(new int[]{1, 2, 3, 5, 5, 5, 8, 9}, 5)); // 返回3,对应的值为5
参考文献: