java的二分查找源码分析

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

前言:

        之前用到二分查找的时候,都是自己手写一个,虽然并不难,但是有的时候会忽略边界条件,然后时间久了还会忘记,然后今天发现,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) 返回

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值