【数据结构和算法】(3)对二分查找算法的进一步优化和理解

本文介绍了Java中二分查找算法的几种优化方式,包括减少循环体内代码行数、返回目标数据在数组最左侧和最右侧的索引。当数组中没有目标数据时,提供了插入位置的计算方法。同时,讨论了如何在有重复元素的数组中找到目标数据最左侧和最右侧的索引。
摘要由CSDN通过智能技术生成

目录

优化1:减少循环体里面执行的代码行数

Java 中的二分查找算法

优化2:返回目标数据在数组数组最左侧的目标索引

优化3:返回目标数据在数组数组最右侧的目标索引

优化4:对返回值进行优化 【最左侧和最右侧】


首先请查看本专题之前的文章关于二分查找算法的描述。

优化1:减少循环体里面执行的代码行数
 /**
     * 再次改动后的代码
     * 优点:目的是减少循环里面的代码执行条数,原来的代码里面有 if 和 else if,现在只有一个判断条件了
     * 缺点:最好的情况就不是 O(1) 了,而是 O(log(n)) 了,和最坏情况相同。
     *
     * @param a      递增数组
     * @param target 需要查找的数据
     * @return
     */
    public static int binarySearch3(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 {
                //target == a[m] 或者 target > a[m],就不能够是 i = m + 1 了
                i = m;
            }
        }
        if (a[i] == target) {
            return i;
        } else {
            return -1;
        }
    }

Java 中的二分查找算法
    // Like public version, but without range checks.
    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.
    }

思考:当数组中没有目标数据时,返回的 -(low + 1) 是什么意思?

解答:-(low + 1) 表示的是将目标数据插入到数组中时,插入的第几个位置的相反数。

例如,若 int[] a = new int[]{2, 5, 6}; target = 4,那么 返回值就是 -2,插入在第2个位置【数据 2 和 5 之间】

思考并实现案例:在一个数组中查找一个数据,若数组中不存在时,就将该数据插入到数组中去,并返回新的数组?

 /**
     * 实现一个功能,在一个数组中查找一个数据,若数组中不存在时,就将该数据插入到数组中去。
     */
    @Test
    public void test4() {
        int[] a = new int[]{2, 5, 6};
        int target = 4;
        int i = Arrays.binarySearch(a, target);
        System.out.println("i = " + i);
        if (i < 0) {
            //数据没有找到
            //获取新的插入点
            int insertIndex = Math.abs(i + 1);
            //实现数组拷贝
            int[] b = new int[a.length + 1];
            System.arraycopy(a, 0, b, 0, insertIndex);
            b[insertIndex] = target;
            //拷贝 insertIndex 的后半部分
            System.arraycopy(a, insertIndex, b, insertIndex + 1, a.length - insertIndex);
            System.out.println(Arrays.toString(b));
        }
    }
​
​
//测试结果
i = -2
[2, 4, 5, 6]
​
Process finished with exit code 0

优化2:返回目标数据在数组数组最左侧的目标索引

提出需求:当一个数组中目标元素是重复的时,我们返回数组最左侧的目标索引,应该怎么实现呢?

    
/**
     * 二分查找返回最左侧索引
     *
     * @param a      升序数组,有重复元素
     * @param target 目标
     * @return 返回目标在数组中的最左侧索引,不存在就返回 -1
     */
    public static int binarySearchLeftmost1(int[] a, int target) {
        int i = 0, j = a.length - 1;
        int candidate = -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 {
                //找到了目标,不马上返回数据,将有右指针指向 m -1,然后继续循环即可
                candidate = m;
                j = m - 1;
            }
        }
        return candidate;
    }

说明,和普通儿茶查找的不同点就是当数据找到了之后的处理方案不一样。

基础版本:当 target == a[m] 时,直接返回索引 m 即可。

优化版本:当 target == a[m] 时,将此时的索引 m 作为一个可选的候选值,然后将右指针指向当前候选值的前一个数据,然后重复二分查找;直到找到最后一个就一定是最左侧的索引。

优化3:返回目标数据在数组数组最右侧的目标索引

提出需求:当一个数组中目标元素是重复的时,我们返回数组最右侧的目标索引,又应该怎么实现呢?

    
/**
     * 二分查找返回最右侧索引
     *
     * @param a      升序数组,有重复元素
     * @param target 目标
     * @return 返回目标在数组中的最右侧索引,不存在就返回 -1
     */
    public static int binarySearchRightmost1(int[] a, int target) {
        int i = 0, j = a.length - 1;
        int candidate = -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 {
                //找到了目标,不马上返回数据,将有左指针只想 m + 1,然后继续循环即可
                candidate = m;
                i = m + 1;
            }
        }
        return candidate;
    }

说明,和查找最左侧数据索引不同,当找到目标数据时,继续向右侧查找,让左指针 i 指向当前数据的下一个索引,然后继续二分循环,直到最后找到的就一定是最右侧的索引。

优化4:对返回值进行优化 【最左侧和最右侧】

在上面的优化2和优化3的代码中,当没有找到目标数据时,返回 -1 这个数据没有什么意义,观察下列代码?

  
 /**
     * 二分查找返回大于等于目标值的最左侧的索引
     *
     * @param a      升序数组,有重复元素
     * @param target 目标
     * @return 二分查找返回大于等于目标值的最左侧的索引
     */
    public static int binarySearchLeftmost2(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 {
                i = m + 1;
            }
        }
        //思考,返回的 i 什么意思
        return i;
    }
​
​
    /**
     * 返回小于等于 target 的最大索引
     *
     * @param a      升序数组,有重复元素
     * @param target 目标
     * @return 返回数组中 <= target 的最大索引
     */
    public static int binarySearchRightmost2(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 {
                i = m + 1;
            }
        }
        return i - 1;
    }

思考:

1:在 binarySearchLeftmost2() 方法中,返回的 i 表示的是什么意思呢?

2:在 binarySearchRightmost2() 方法中,返回的 i - 1 表示的什么意思呢?

解答:

1:在binarySearchLeftmost2中,i 表示在数组中,大于等于目标数据最左侧【最小】的索引。等于表示目标数据存在,大于表示目标数据不存在。

2:在 binarySearchRightmost2 中,i - 1 表示在数据中,小于等于目标数据最右侧【最大】的索引。等于表示目标数据存在,大于表示目标数据不存在。

测试:

  @Test
    public void test7(){
        int[] a = new int[]{2, 5, 6, 6, 6, 6, 7, 9, 23};
        // 二分查找8 返回大于等于目标值的最左侧的索引,应该是 9 对应的索引为 7
        int i1 = BinarySearch.binarySearchLeftmost2(a, 8);
        //返回大于等于6最左侧的做引,返回第一个6的索引,为 2
        int i2 = BinarySearch.binarySearchLeftmost2(a, 6);
        System.out.println("i1 = " + i1);
        System.out.println("i2 = " + i2);
​
        // 返回 <= target 的最靠右的索引 ,应该是元素 7,对应的索引是 6
        int i3 = BinarySearch.binarySearchRightmost2(a, 8);
        //返回 <= 6 的最右侧索引,返回最后一个 6,对应的索引是 5
        int i4 = BinarySearch.binarySearchRightmost2(a, 6);
        System.out.println("i3 = " + i3);
        System.out.println("i4 = " + i4);
    }
    
    //测试结果
i1 = 7
i2 = 2
i3 = 6
i4 = 5
​
Process finished with exit code 0

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值