【算法】二分查找

二分查找是一种高效的查找算法,适用于有序数组。通过不断缩小查找范围,查找时间复杂度为O(log2n)。算法中设置左右指针,每次比较中间元素与目标值,根据比较结果调整查找范围。此外,二分查找还可以用于找到比目标值大或小的临界位置。注意在某些情况下,为了避免数值溢出,中间位置的计算需采用特定方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概念

二分查找(Binary Search)是一种效率较高的查找方式,它的时间复杂度为O(logn)

二分查找要求数组按关键字有序排列,并设置两个指针leftright分别指向数组的开头和末尾,每次查找leftright的中间项[mid]

设数组非递减排列:

  1. 若目标值target等于中间项[mid],则找到元素;
  2. 若目标值target小于中间项[mid],则前往当前范围的左半部分查找,将right置为mid-1
  3. 若目标值target大于中间项[mid],则前往当前范围的右半部分查找,将left置为mid+1
  4. 若指针left跑到了right的右边,代表数组已查询完毕,且数组中没有目标值。由此可得循环的结束条件为left>right

要点

  1. 请牢记二分查找算法的时间复杂度O(log2n)

  2. 二分查找要求线性表必须以顺序结构存储,并且表中元素按关键字有序排列

  3. 二分查找一般设置2个指针,一个指针left指向数组开头另一个指针right指向数组末尾。查找循环的有效条件left<=right

    /* C++ */
    // n为数组长度
    for(int left=0, right=n-1;left<=right;) {...}
    
  4. 每次循环需要对leftright中间一位进行判断。以C++为例,由于除法运算符/会将整数除法结果自动舍去小数点,因此每次的目标为leftright的中间位(right-left结果为偶数)或中间偏左一位(right-left结果为奇数)。

    中间位mid的计算有两种方式:

    /* C++ */
    // n为数组长度
    // int left = 0;
    // int right = n - 1;/* 式1 */
    int mid = (right + left) >> 1;/* 式2 */
    int mid = ((right - left) >> 1) + left;
    

    将式1转化为数学公式如下:
    m i d = l e f t 2 + r i g h t 2 mid=\frac{left}{2}+\frac{right}{2} mid=2left+2right
    式1非常易懂,将两数相加除以2即为他们的中位数。但是这个写法已经这么简单了,为什么还会出现式2呢?

    原因是1式会产生数值溢出。我们粗暴一点,假设当前leftright皆为INT_MAX,即7FFFFFFFH,则两数相加就超出了int能表示的范围上限,最终mid的值就不正确了。将式2转换为数学公式如下:
    m i d = r i g h t 2 − l e f t 2 + l e f t mid=\frac{right}{2}-\frac{left}{2}+left mid=2right2left+left

    可以看到只是进行了一次简单的改变,但式2是不会出现数值溢出问题的。

    当然1,在Python等语言中基本上是不会出现这种问题的,可以不用考虑。不过以这些语言为主力语言的小伙伴了解一下这个问题还是有益的。

    当然2,你要这么写也没人拦你(也确实能出正确结果:

    /* C++ */
    int mid = (long long(left) + long long(right)) >> 1;
    

    但万一leftright本身就是双长整型的最大值呢?即使mid定义为long long最终也会产生数值溢出的。所以无论怎样还是要用到式2的。

  5. leftright在什么时候变化可能会根据实际情况改变,这里仅记录最基础的改变时机。

    /* C++ */
    for (int left = 0, right = n - 1; left <= right;)
    {
        int mid = ...;
        if (target == nums[mid])
            return mid;
        else if (target < nums[mid])
            right = mid - 1;
        else
            left = mid + 1;
    }
    
  6. 二分查找不仅可以用于查找确切值,还能查找到比某个数大,或比某个数小的临界位置。

    (这里指的当然不是找到那个数target然后左边就比它小,右边就比它大的这种显而易见的问题。)

    在基础的算法结构上修改一些细节即可达到此目的。 参考案例[278. 第一个错误的版本]和以下案例:

    /* C++ */
    // 查找数组nums(升序排列)中第一个大于target的值。
    int left = 0, right = nums.size() - 1;
    int sub = nums.size();
    while (left <= right)
    {
        int mid = ((right - left) >> 1) + left;
        if (target < nums[mid])
        {
            sub = mid;
            right = mid - 1;
        }
        else
        {
            left = mid + 1;
        }
    }
    // 此时sub为数组nums中第一个比target的值的下标
    
    • target<nums[mid],则代表中间项大于target,第一个比target大的值要么在该项之前,要么就是它自身。由于没有变量能够,因此需要一个额外的变量sub存放当时的mid,并且置rightmid-1

    • target>=nums[mid],则代表中间项小于target,第一个比target大的值必定在该项之后,因此我们将left置为mid+1

    上述代码在循环结束后,sub为数组nums中第一个比target的值的下标。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

亡心灵

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

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

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

打赏作者

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

抵扣说明:

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

余额充值