当前位置:   article > 正文

算法基础——关于二分查找的那些事_小数二分

小数二分


二分查找的思路

二分查找也称为折半查找(binary search),适用于顺序存储结构的线性表,且表中元素是有序排列的情况。对于一个顺序排列的数组而言,当查找区间有不止一个元素的时候(即左边界不超过右边界),每次循环取当前区间中间的元素与目标值比较,根据以下三种比较结果判断跳出循环或更新左右边界:

  1. 目标值 = 中间元素
    此时正好在在当前区间的中间找到目标值,跳出循环。
  2. 目标值 > 中间元素
    表示此时目标值在以中间元素为界的右区间,需要移动左边界以在右区间继续进行查找。
  3. 目标值 < 中间元素
    表示此时目标值在以中间元素为界的左区间,需要移动右边界以在左区间继续进行查找。
    在这里插入图片描述

算法复杂度

由于二分查找的适用场景为顺序表,因此空间复杂度为 O ( 1 ) O(1) O(1)
假设数组中有n个元素,且为顺序排列,如果进行进行二分查找过程如下:

  • Step 1:在长度为n的区间中查找
  • Step 2:在长度为 n / 2的区间中查找
  • Step 3:在长度为 (n / 2) / 2 的区间中查找
  • ……
  • Step x:在长度为 n / (2 ^ (x - 1)) 的区间中查找

由此可见,若在最坏情况下经过x次查找寻找到答案,最终区间长度应为1,即:
n / 2 x − 1 = 1 n / 2^{x - 1} = 1 n/2x1=1 x = l o g 2 n x = log_2n x=log2n因此,二分查找的时间复杂度为 O ( l o g 2 n ) O(log_2n) O(log2n)

几种不同的二分查找

1. 普通二分

假设一个已经排好序的数组num中有n个元素,t为需要查找的目标值。

int binary_search(int t) {
	int l = 0, r = n - 1;
    while (l <= r) {
        int mid = (l + r) / 2;
        if (num[mid] == x) {
            return 1;
        } else if (x > num[mid]) {
            l = mid + 1;
        } else {
            r = mid - 1;
        }
    }
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

缩减查找区间的时候,需要注意的是,每次都略过了mid这个中间元素。这是因为在前面的判断中已经确定了num[mid]不是我们需要的值,所以在下一次循环查找中,不需要将其包含在查找区间中。

2. 二分答案

在实际的问题中,有时二分的对象未必是答案,而是需要将二分的对象经过某些特殊运算得到的值进行判断,由此得到满足条件的解。此类型问题的答案通常具有单调性,且多数为边界值解(如求最大的最小值等),这类问题被统称为二分答案问题

对于二分答案类型问题的思路,可以从二分的对象入手进行推导:

  • 首先对于一组样例输入数据,找到符合条件的目标边界值。
  • 取该值的相邻值进行判断,如果能满足条件则该侧均能满足条件(1),不满足条件则该侧均不能满足条件(0)。
  • 据此可以建立起对应的代码模型,根据不同输入样例求解。

因此,二分答案的结果可以近似为两种类型:00001111型和11110000型。

两种类型的代码模版如下:

  • 00001111型

    while (l != r) {
    	int mid = (l + r) / 2;
    	if (func(mid) >= t) { // t为目标查找数
    		r = mid;
    	} else {
    		l = mid + 1;
    	}
    }
    return r; // return l;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注意:

    • 移动右边界时,mid可能正好是答案,在新的查找区间中需要包含mid。
    • 移动左边界时,经过前面判断可知mid不是答案,所以在新的查找区间中不需要包含mid。
    • 最终左边界 = 右边界,查找区间不存在,此时l和r均为答案,二者任选其一即可。

  • 11110000型

    while (l != r) {
    	int mid = (l + r + 1) / 2;
    	if (func(mid) <= t) { // t为目标查找数
    		l = mid;
    	} else {
    		r = mid - 1;
    	}
    }
    return l; // return r;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注意:

    • 在查找区间只剩下两个元素的时候(10),如果仍然使用 m i d = ( l + r ) / 2 mid = (l + r) / 2 mid=(l+r)/2来计算mid, m i d = l mid = l mid=l将会永远成立,查找区间不会缩小,导致死循环。所以需要使用 m i d = ( l + r + 1 ) / 2 mid = (l + r + 1) / 2 mid=(l+r+1)/2向上取整来计算mid的值。
    • 移动左边界时,mid可能正好是答案,在新的查找区间中需要包含mid。
    • 移动右边界时,经过前面判断可知mid不是答案,所以在新的查找区间中不需要包含mid。
    • 最终左边界 = 右边界,查找区间不存在,此时l和r均为答案,二者任选其一即可。

3. 小数二分

对于小数二分,推导思路与上述两种问题一样,唯一需要注意的地方是循环的条件发生了变化。因为二分的对象为浮点型数据,查找区间应为开区间而不是闭区间了,所以不需要再判断左右边界是否相等,而是通过判断左右边界之间差值的精度(如r - l > 0.00001),近似的判断左右边界是否重合。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/624436
推荐阅读
相关标签
  

闽ICP备14008679号