前言:
之前用到二分查找的时候,都是自己手写一个,虽然并不难,但是有的时候会忽略边界条件,然后时间久了还会忘记,然后今天发现,Java其实已经实现了数组的二分查找,这里就分析一下它的源码
1:该方法在 Arrays.java 这个类里面,调用的话可以直接使用 Arrays.binarySearch(), 它有好多实现,这里就拿Arrays.binarySearch(int[] a, int key) 举例。测试代码如下:
public class test {
@Test
public void test() {
int a[] = {1, 4, 8};
System.out.println(Arrays.binarySearch(a, 1));
System.out.println(Arrays.binarySearch(a, 4));
System.out.println(Arrays.binarySearch(a, 8));
System.out.println(Arrays.binarySearch(a, 9));
System.out.println(Arrays.binarySearch(a, 0));
System.out.println(Arrays.binarySearch(a, 5));
}
}
对应的输出结果:
0
1
2
-4
-1
-3
我们发现,当要搜索的值正好是在数组中出现过的话,那么就会返回其对应的下标,但是如果要搜索的值不在数组中,竟然返回了负数,这里就需要看下源码来探索下它后面到底做了什么了,源码如下:
首先调用了该方法,然后该方法紧接着调用了binarySearch0方法
public static int binarySearch(int[] a, int key) {
return binarySearch0(a, 0, a.length, key);
}
private static int binarySearch0(int[] a, int fromIndex, int toIndex,
int key) {
int low = fromIndex;
int high = toIndex - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
int midVal = a[mid];
if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found.
}
当看到 binarySearch0 方法的时候,我们其实已经应该就很熟悉了,它就是使用双指针的方法,取两边边界坐标,然后取平均值,即中间节点,如果中间节点小于要搜索的目标值 key, 则 low = mid + 1, 即左边指针向右移动,在剩下的范围里面找,同理,如果中间节点的值大于目标值key,说明 key 只可能出现在 [low, mid - 1] 这个区间内,这也是二分搜索的核心,一下排除一半,然后如果 midVal == key, 说明我们找到了,就返回对应的下标值,这跟我们上边看到的结果是一致的,那么我们就来看下为什么会返回负数?
我们留意到该方法最后一行返回了 -(low + 1), 这对应了三种情况,
1:要搜索的值大于数组中所有的值, 此时,在遍历的过程中只会发生 low 的移动,直到 low > high 跳出 while 循环,而 high 的初始值为 a.length - 1, 故此时 low = a.length,此时返回的为 -(a.length + 1)
2:要搜索的值小于数组中所有的值,此时在遍历过程中只会发生 high 向前移动,直到 high < low 跳出 while 循环,而 low 的初始值为 0, 故最后返回 -(low + 1) 即为返回 -1
3:要搜索的值的大小范围处于数组之中,但并没有完全相等的值与之匹配,此时我们只需要考虑最后一步,最后一步有两种情况:
3-1:low = high: 此时 mid = low = high,这种情况说明数组中当前位置是最有可能与 key 值匹配的位置了,因为其他的已经被排除了,如果当前元素小于key, 就会触发如下操作,low = mid + 1,这个位置对应数组中第一个大于 key 的元素下标,如果当前元素大于 key, 那么 low 的值不变,high = mid - 1, 此时 low 对应的还是数组中第一个大于 key 的元素的下标。
3-2: low = high - 1: 此时 mid = low = high - 1, 此时如果 midVal > key, 会发生 high = high - 1,就会触发 high = mid - 1的操作,然后跳出循环,此时 low 也是对应数组中第一个大于key的元素的下标,而如果midVal < key, 会触发 low = mid + 1的操作,然后就又会出现 3-1 提到的 low = high的情况,而上面已经分析过了,low 对应数组中第一个大于key的元素的下标
综上所述,循环结束之后,low 对应的是数组中第一个大于 key 值的元素的下标, 然后其实第三种情况跟情况 1 和 2是类似的,按理说直接返回这个 low 对应的下标岂不是美滋滋,java 的开发人员偏偏又给他加了个1 然后又取了个负数,即 -(low + 1),那么为什么要这样多此一举呢?直接返回 low 它不香么?
为什么要返回 -(low + 1)?
首先返回负数是告诉你,数组中没有匹配到的值,这样能理解吧?那你是不是又要问那返回 -low 不就行了么,多方便,可是别忘了还有这么一种情况,你要搜索的值比数组中任何一个都要小,即上面情况 2 ,此时 low = 0, 那么 -low 呢,哈哈,还是0,所以说这里取 -(low + 1) 还是很合理的
所以如果我们想要用该方法找到数组中第一个大于指定 key 元素的值的话,别忘了对返回值再取个相反数,然后再减个 1 哦,即-(-(low + 1)) - 1 = low + 1 - 1 = low, 就是我们想要的了。
总结:
1: 如果要查找的元素能在数组中匹配到,那么就返回对应值的下标
2:如果要查找的元素在数组中匹配不到, 就先找到数组中第一个大于该元素的值的下标 low, 然后取 -(low + 1) 返回

本文深入剖析了Java中Arrays类的binarySearch方法实现原理,详细解释了如何通过双指针法进行二分查找,以及为何返回值在未找到元素时为负数。文章还探讨了如何利用返回值定位数组中第一个大于指定key的元素。

被折叠的 条评论
为什么被折叠?



