二分找查法

二分找查法

二分查找算法也称折半查找,是一种非常高效的工作于有序数组的查找算法。后续的课程中还会学习更多的查找算法,但在此之前,不妨用它作为入门。

二分查找基础版

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

  • 如果找到返回索引

  • 如果找不到返回 $-1$

前提给定一个内含 $n$ 个元素的有序数组 $A$,满足 $A{0}\leq A{1}\leq A{2}\leq \cdots \leq A{n-1}$,一个待查值 $target$
1设置 $i=0$,$j=n-1$
2如果 $i \gt j$,结束查找,没找到
3设置 $m = floor(\frac {i+j}{2})$ ,$m$ 为中间索引,$floor$ 是向下取整($\leq \frac {i+j}{2}$ 的最小整数)
4如果 $target < A_{m}$ 设置 $j = m - 1$,跳到第2步
5如果 $A_{m} < target$ 设置 $i = m + 1$,跳到第2步
6如果 $A_{m} = target$,结束查找,找到了

java 实现

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;
}
  • $i,j$ 对应着搜索区间 $[0,a.length-1]$(注意是闭合的区间),$i<=j$ 意味着搜索区间内还有未比较的元素,$i,j$ 指向的元素也可能是比较的目标

    • 思考:如果不加 $i==j$ 行不行?

    • 回答:不行,因为这意味着 $i,j$ 指向的元素会漏过比较

  • $m$ 对应着中间位置,中间位置左边和右边的元素可能不相等(差一个),不会影响结果

  • 如果某次未找到,那么缩小后的区间内不包含 $m$

二分查找改变版

另一种写法

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

  • $i,j$ 对应着搜索区间 $[0,a.length)$(注意是左闭右开的区间),$i<j$ 意味着搜索区间内还有未比较的元素,$j$ 指向的一定不是查找目标

    • 思考:为啥这次不加 $i==j$ 的条件了?

    • 回答:这回 $j$ 指向的不是查找目标,如果还加 $i==j$ 条件,就意味着 $j$ 指向的还会再次比较,找不到时,会死循环

  • 如果某次要缩小右边界,那么 $j=m$,因为此时的 $m$ 已经不是查找目标了

衡量算法好坏

时间复杂度

下面的查找算法也能得出与之前二分查找一样的结果,那你能说出它差在哪里吗?

public static int search(int[] a, int k) {
    for (
        int i = 0;
        i < a.length;
        i++
    ) {
        if (a[i] == k) {
            return i;
        }
    }
    return -1;
}

考虑最坏情况下(没找到)例如 [1,2,3,4] 查找 5

  • int i = 0 只执行一次

  • i < a.length 受数组元素个数 $n$ 的影响,比较 $n+1$ 次

  • i++ 受数组元素个数 $n$ 的影响,自增 $n$ 次

  • a[i] == k 受元素个数 $n$ 的影响,比较 $n$ 次

  • return -1,执行一次

粗略认为每行代码执行时间是 $t$,假设 $n=4$ 那么

  • 总执行时间是 $(1+4+1+4+4+1)*t = 15t$

  • 可以推导出更一般地公式为,$T = (3*n+3)t$

如果套用二分查找算法,还是 [1,2,3,4] 查找 5

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;
}
  • int i = 0, j = a.length - 1 各执行 1 次

  • i <= j 比较 $floor(\log_{2}(n)+1)$ 再加 1 次

  • (i + j) >>> 1 计算 $floor(\log_{2}(n)+1)$ 次

  • 接下来 if() else if() else 会执行 $3* floor(\log_{2}(n)+1)$ 次,分别为

    • if 比较

    • else if 比较

    • else if 比较成立后的赋值语句

  • return -1,执行一次

结果:

  • 总执行时间为 $(2 + (1+3) + 3 + 3 * 3 +1)*t = 19t$

  • 更一般地公式为 $(4 + 5 * floor(\log_{2}(n)+1))*t$

注意:

左侧未找到和右侧未找到结果不一样,这里不做分析

二分查找平衡版

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

思想:

  1. 左闭右开的区间,$i$ 指向的可能是目标,而 $j$ 指向的不是目标

  2. 不奢望循环内通过 $m$ 找出目标, 缩小区间直至剩 1 个, 剩下的这个可能就是要找的(通过 $i$)

    • $j - i > 1$ 的含义是,在范围内待比较的元素个数 > 1

  3. 改变 $i$ 边界时,它指向的可能是目标,因此不能 $m+1$

  4. 循环内的平均比较次数减少了

  5. 时间复杂度 $\Theta(log(n))$

二分查找 Java 版

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,3,5,6]$ 要插入 $2$ 那么就是找到一个位置,这个位置左侧元素都比它小

    • 等循环结束,若没找到,low 左侧元素肯定都比 target 小,因此 low 即插入点

  • 插入点取负是为了与找到情况区分

  • -1 是为了把索引 0 位置的插入点与找到的情况进行区分

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值