数据结构与算法【二分查找】Java实现

需求:在有序数组 A 内,查找值target

  • 如果找到返回索引
  • 如果找不到返回 -1

前提

给定一个内含 n 个元素的有序数组 A,一个待查值 target

1

设置 i = 0,j = n-1

2

如果 i > j,结束查找,没找到

3

设置 m = (i+j)/2 ,m 为中间索引并向下取整

4

如果 target < A[m] 设置 j = m - 1,跳到第2步

5

如果 A[m] < target 设置 i = m + 1,跳到第2步

6

如果 A[m] = target,结束查找,找到了

二分查找法的时间复杂度为 O(log(n))。 

代码实现基础版

public class search_binary {
    public static void main(String[] args) {
        int[] a = {9, 18,21, 28, 32,40,46};
        int index = search(a, 32);
        System.out.println("查找结果为:" + index);
    }

    public static int search(int[] a, int target) {
        int i = 0;
        int j = a.length - 1;
        while (i <= j) {
            int m = (i + j) / 2;
            if (a[m] < target) {
                //说明要查找的数在右边
                i = m + 1;
            } else if (a[m] > target) {
                j = m - 1;
            } else {
                return m;
            }
        }
        return -1;
    }
}

由于Java中int类型会自动向下取整,因此我们不需要考虑m带小数的情况。

查找过程如下

但是它仍存在一些问题。那就是Integer数值取值问题,m有可能会变为负数,情景再现。

    public static void main(String[] args) {
        int i = 0;
        int j = Integer.MAX_VALUE;//j表示int类型所能表达的最大数
        int m = (i + j) / 2;
        System.out.println(m);
        //假设目标值在m右侧
        i = m + 1;
        m = (i + j) / 2;
        System.out.println(m);
    }

1073741823

-536870912

之所以会出现这种问题,是因为m的值超出了int所能表达的最大值。在计算机中,一个数字最后都会由二进制表示,而int类型能表达的范围是-2147483648~2147483647,最大值转化为二级制为

可以看到第一位数字为0,这是因为Java中,是需要把二进制数字中的第一位数字当作符号位的(0为正,1为负)。超过最大值的二进制表示,第一位就会变为1,在Java中自然会被识别成一个负数。

为了避免这个问题,我们可以采用右移运算,也就是将所有的二进制右移一位(相当于除以2)。

具体代码实现如下

public static int binarySearch(int[] a, int target) {
    int i = 0, j = a.length - 1;
    while (i <= j) {
        int m = (i + j) >>> 1;
        if (target < a[m]) {          // 在左边
            j = m - 1;
        } else if (a[m] < target) {   // 在右边
            i = m + 1;
        } else {
            return m;
        }
    }
    return -1;
}

代码实现改动版 

    public static int binarySearch(int[] a, int target) {
        int i = 0, j = a.length;
        while (i < j) {
            int m = (i + j) >>> 1;
            if (target < a[m]) {			// 在左边
                j = m;
            } else if (a[m] < target) {		// 在右边
                i = m + 1;
            } else {
                return m;
            }
        }
        return -1;
    }

与基础版相比,改动版就是修改了 j 的边界值,将 j 的下次一次取值变为m。首先我们需要清楚的是,a[m]不是目标值,下标为m的数字是已经判断过了的,因此可以充当 j 的边界值。

流程如下,我们查找29的下标

但是这种实现方式存在一个问题,那就是目标值在a[m]右侧时,每次都需要先进行一次是否在a[m]左侧的操作。这就造成了每次不必要的查找,也会导致,查找左侧的元素的效率要比查找右侧的效率要高。对此,我们还可以进行优化。

具体代码如下

public static int binarySearchBalance(int[] a, int target) {
    int i = 0, j = a.length;
    while (1 < j - i) {
        int m = (i + j) >>> 1;
        if (target < a[m]) {
            j = m;
        } else {
            i = m;
        }
    }
    return (a[i] == target) ? i : -1;
}

while中的判断条件为元素中剩余查找的元素个数,当剩余1个时,将退出循环,然后在外部进行判断是否是目标值。

二分查找,数组中的重复元素,其实返回的值为第一次查找到的下标,如果添加了需求,要求返回重复元素的最左侧的下标就不能满足该需求,因此,我们还需要对该二分查找进行代码补充

public static int binarySearch(int[] a, int target) {
    int i = 0, j = a.length - 1;
    int tempDate = -1;
    while (i <= j) {
        int m = (i + j) >>> 1;
        if (target < a[m]) {
            j = m - 1;
        } else if (a[m] < target) {
            i = m + 1;
        } else {
            //修改返回值处的代码
            tempDate = m; // 记录当前目标值的下标
            //查看左侧是否还存在相同的目标值
            j = m -1;
        }
    }
    return -1;
}

Java中的二分查找

Java中的二分查找的实现在Arrays类中,对应代码如下

    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.
    }

与基础版所写基本没有差别,区别在于返回值。根据文档意思,这个返回值的意思是(-插入点)-1。意思是,如果没有查找到目标值,那么就会返回目标值应该插入数组中的下标值加一并取负数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zmbwcx2003

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值