二分查找(自用笔记)

二分查找

基础版

重点记录

int m = (i + j) / 2 写成 int m = (i + j) >>> 1

>>> 1 二进制右移一位

假设 j 为 int 可以储存的最大数值, 那么在加上i之后,就会二进制溢出,结果不准确,可以通过>>> 1 二进制右移一位准确的计算数量除以2

    /**
     * 二分查找 基础版
     *
     * @param a      待查找的数组
     * @param target 带查找的参数,找不到返回-1
     * @return
     */
    public static int binarySearchBasic(int[] a, int target) {
        // 左指针
        int i = 0;
        // 右指针
        int j = a.length - 1;
        // todo 为什么是(i <= j)而不是(i < j)
        // 如果 i < j 的话 那么当 i = j 时 左右指针一起指向一个元素,不在进行一次判断会检索不到这个元素。
        while (i <= j) {
            // 查找中间值
            int m = (i + j) / 2;
            if (target < a[m] ) {
                j = m - 1;
            } else if (a[m] < target ) {
                i = m + 1;
            } else {
                return a[m];
            }
        }
        return -1;
    }

改动版

    /**
     * 二分查找 改动版
     *
     * @param a      待查找的数组
     * @param target 带查找的参数,找不到返回-1
     * @return
     */
    public static int binarySearchAlternative(int[] a, int target) {
        // 左指针
        int i = 0;
        // 右指针
        // 改动1
        int j = a.length; 
        // 改动2
        while (i < j) {
            // 查找中间值
            int m = (i + j) / 2;
            if (target < a[m]) {
                // 改动3
                j = m;
            } else if (a[m] < target) {
                i = m + 1;
            } else {
                return a[m];
            }
        }
        return -1;
    }

与基础版区别

  • 基础版:i 和 j 分别指向了一个元素
  • 改动版:i 指向一个元素,j 含义变为边界,不参与运算,代码为 int j = a.length;

平衡版

    /**
     * 二分查找稳定版
     *
     * @param a      待查找的数组
     * @param target 带查找的参数,找不到返回-1
     * @return
     */
    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;
    }

与改动版区别

  • 改动版

    • 代码中如果查找元素在最左侧,循环N次就会走N次来查找元素
    • 如果元素在最右侧,循环N次会走N*2次 if (if target < a[m]条件不满足后走 else if a[m] < target)
    • 所以根据查找的元素位置不同,判断的次数不稳定
  • 平衡版

    • 循环中判断的次数稳定

    • i 指的可能是元素,j 依然指边界

    • 不在循环中去比较数据

    • 根据while (1 < j - i) 判断剩最后一个元素时结束循环,并判断这个元素的值是否等于被查找的值。

JAVA版

    /**
     * 二分查找 JAVA版
     * @param a
     * @param fromIndex
     * @param toIndex
     * @param key
     * @return
     */
    private static int binarySearch0(long[] a, int fromIndex, int toIndex,
                                     long key) {
        int low = fromIndex;
        int high = toIndex - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            long 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 是为了把索引 0 位置的插入点与找到的情况进行区分 例如 待插入的下标为0 则返回 -1

Leftmost 与 Rightmost

有时我们希望返回的是最左侧的重复元素,如果用 Basic 二分查找

  • 对于数组 [ 1 , 2 , 3 , 4 , 4 , 5 , 6 , 7 ] [1, 2, 3, 4, 4, 5, 6, 7] [1,2,3,4,4,5,6,7],查找元素4,结果是索引3

  • 对于数组 [ 1 , 2 , 4 , 4 , 4 , 5 , 6 , 7 ] [1, 2, 4, 4, 4, 5, 6, 7] [1,2,4,4,4,5,6,7],查找元素4,结果也是索引3,并不是最左侧的元素

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 {
            candidate = m; // 记录候选位置
            j = m - 1;     // 继续向左
        }
    }
    return candidate;
}

如果希望返回的是最右侧元素

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 {
            candidate = m; // 记录候选位置
            i = m + 1;	   // 继续向右
        }
    }
    return candidate;
}

应用

对于 Leftmost 与 Rightmost,可以返回一个比 -1 更有用的值

Leftmost 改为

public static int binarySearchLeftmost(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; 
}
  • leftmost 返回值的另一层含义: < t a r g e t \lt target <target 的元素个数
  • 小于等于中间值,都要向左找

Rightmost 改为

public static int binarySearchRightmost(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;
}
  • 大于等于中间值,都要向右找

几个名词

在这里插入图片描述

范围查询

  • 查询 x < 4 x \lt 4 x<4 0.. l e f t m o s t ( 4 ) − 1 0 .. leftmost(4) - 1 0..leftmost(4)1
  • 查询 x ≤ 4 x \leq 4 x4 0.. r i g h t m o s t ( 4 ) 0 .. rightmost(4) 0..rightmost(4)
  • 查询 4 < x 4 \lt x 4<x,$rightmost(4) + 1 … \infty $
  • 查询 4 ≤ x 4 \leq x 4x l e f t m o s t ( 4 ) . . ∞ leftmost(4) .. \infty leftmost(4)..∞
  • 查询 4 ≤ x ≤ 7 4 \leq x \leq 7 4x7 l e f t m o s t ( 4 ) . . r i g h t m o s t ( 7 ) leftmost(4) .. rightmost(7) leftmost(4)..rightmost(7)
  • 查询 4 < x < 7 4 \lt x \lt 7 4<x<7 r i g h t m o s t ( 4 ) + 1.. l e f t m o s t ( 7 ) − 1 rightmost(4)+1 .. leftmost(7)-1 rightmost(4)+1..leftmost(7)1

求排名 l e f t m o s t ( t a r g e t ) + 1 leftmost(target) + 1 leftmost(target)+1

  • t a r g e t target target 可以不存在,如: l e f t m o s t ( 5 ) + 1 = 6 leftmost(5)+1 = 6 leftmost(5)+1=6
  • t a r g e t target target 也可以存在,如: l e f t m o s t ( 4 ) + 1 = 3 leftmost(4)+1 = 3 leftmost(4)+1=3

求前任(predecessor) l e f t m o s t ( t a r g e t ) − 1 leftmost(target) - 1 leftmost(target)1

  • l e f t m o s t ( 3 ) − 1 = 1 leftmost(3) - 1 = 1 leftmost(3)1=1,前任 a 1 = 2 a_1 = 2 a1=2
  • l e f t m o s t ( 4 ) − 1 = 1 leftmost(4) - 1 = 1 leftmost(4)1=1,前任 a 1 = 2 a_1 = 2 a1=2

求后任(successor) r i g h t m o s t ( t a r g e t ) + 1 rightmost(target)+1 rightmost(target)+1

  • r i g h t m o s t ( 5 ) + 1 = 5 rightmost(5) + 1 = 5 rightmost(5)+1=5,后任 a 5 = 7 a_5 = 7 a5=7
  • r i g h t m o s t ( 4 ) + 1 = 5 rightmost(4) + 1 = 5 rightmost(4)+1=5,后任 a 5 = 7 a_5 = 7 a5=7

求最近邻居

  • 前任和后任距离更近者
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值