“二分”一定要有序么

本文详细介绍了二分查找算法的工作原理,包括其在有序数组中的高效性以及如何利用分而治之的思想进行搜索。文章通过实例展示了如何在不同问题中应用二分查找,强调虽然通常需要数组有序,但在合理划分逻辑后,非有序数组也可使用此方法。
摘要由CSDN通过智能技术生成

二分查找,又称为折半查找(Binary Search),是一种效率较高的搜索算法。采用 折半查找判定树 更能直观看出其时间复杂度为 O(log N)。

该算法采用分而治之的思想,要求线性表中的元素按关键字有序排列,其基本流程如下:

  1. 确定搜索范围:确定要在哪个区间内进行搜索。设置两个指针,一个指向数组的起始位置,另一个指向数组的结束位置。
  2. 计算中间元素的索引:计算起始指针 start 与结束指针 end 所指位置的中间元素的索引 mid = (start + end) / 2。
  3. 比较中间元素:将目标元素与中间元素进行比较。
    · 如果目标元素 == 中间元素,搜索完成,找到了目标元素的位置。
    · 如果目标元素 < 中间元素,说明目标元素可能在左半部分,将搜索范围缩小为左半部分。
    · 如果目标元素 > 中间元素,说明目标元素可能在右半部分,将搜索范围缩小为右半部分。
  4. 更新搜索范围: 根据比较的结果,更新指针的位置,缩小搜索范围。
    · 如果目标元素在左半部分,更新 end = mid - 1。
    · 如果目标元素在右半部分,更新 start = mid + 1。
  5. 重复步骤:重复步骤 2-4,直到找到目标元素或确定目标元素不在数组中。

下面通过几道题目理解一下二分查找的思想。注意 : 数组不一定都要有序哦!

问题1

给定一个有序的整形数组,查找某个数字是否存在于该数组中。

private static boolean exit(int[] arr, int num) {
    if (arr == null || arr.length == 0) {
        return false;
    }
    int l = 0;
    int r = arr.length - 1;
    while (l <= r) {
        int mid = l + ((r - l) >> 1);
        if (arr[mid] < num) {
            l = mid + 1;
        } else if (arr[mid] > num) {
            r = mid - 1;
        } else {
            return true;
        }
    }
    return false;
}

这是最经典的二分查找代码,每次将查找区间划分成两部分,通过判断 arr[mid]mid 的大小关系确定下一次查找操作所在的区域。
该方法的成立需要数组有序

问题2

给定一个升序的整形数组,查找 小于等于 某个数字最 右 侧的位置。

例如:给定数组 arr=[2, 2, 2, 3, 3, 4, 4, 5, 5, 5, 6] 找出小于等于 3 最右侧的位置,即返回 5 。表示前 5 个数都符合 ≤3 的要求。

public static int smallOrEqualRightIndex(int[] arr, int num) {
    int index = -1;
    int l = 0;
    int r = arr.length - 1;
    while (l <= r) {
        int mid = l + ((r - l) >> 1);
        if (arr[mid] <= num) {
            index = mid;
            l = mid + 1;
        } else {
            r = mid - 1;
        }
    }
    return index + 1;
}

与问题 1 的代码很类似,该问题是要确定 ≤ ,因此当 arr[mid] == num 时,依然需要做二分,继续向右侧寻找,直到找到最右侧 ( 即下一位置值大于 num )为止。
该方法的成立需要数组有序

问题3

给定一个升序的整形数组,查找 大于等于 某个数字最 左 侧的位置。

例如:给定数组 arr=[2, 2, 2, 3, 3, 4, 4, 5, 5, 5, 6] 找出大于等于 3 最左侧的位置,即返回 4 。表示从第 4 个数开始之后的数都符合 ≥3 的要求。

public static int bigOrEqualLeftIndex(int[] arr, int num) {
    int index = -1;
    int l = 0;
    int r = arr.length - 1;
    while (l <= r) {
        int mid = l + ((r - l) >> 1);
        if (arr[mid] >= num) {
            index = mid;
            r = mid - 1;
        } else {
            l = mid + 1;
        }
    }
    return index + 1;
}

会做问题 2 后,该问题的解答也就轻而易举了。当 arr[mid] == num 时,依然需要做二分,继续向左侧寻找,直到找到最左侧 ( 即上一位置值小于 num )为止。
该方法的成立需要数组有序

问题4

给定一个任意两相邻的数一定不相等的无序数组,找到该数组中任意一个局部最小值并返回其下标。

局部最小值:
· 若 i 非数组的首尾下标,则满足 arr[i-1] < arr[i] < arr[i+1]
· 若 i == 0,则满足 arr[0] < arr[1]
· 若 i == n - 1,则满足 arr[n-1] < arr[n-2]

public static int getLessIndex(int[] arr) {
    if (arr == null || arr.length == 0) {
        return -1;
    }
    int n = arr.length - 1;
    if (arr[0] < arr[1] || n == 0) {
        return 0;	
    }
    if (arr[n] < arr[n - 1]) {
        return n;
    }
    int l = 1;
    int r = n - 1;
    while (l < r) {
        int mid = l + ((r - l) >> 1);
        if (arr[mid] > arr[mid + 1]) {
            l = mid + 1;
        } else if (arr[mid] > arr[mid - 1]) {
            r = mid - 1;
        } else {
            return mid;
        }
    }
    return l;
}

该问题是较为特殊的使用二分查找的例子,题目并未给出有序的数组。对于边界条件的判断较为简单,当符合边界判断时直接返回。

当进入到 while 条件中时,局部最小值一定存在于数组非两边边界的内部。题目只要求找出任意一个局部最小值,因此,若 arr[mid] > arr[mid + 1]arr[mid] > arr[mid - 1] 时,可以大胆抛弃掉一半,在另外一半中进行查找。只有符合条件时才返回下标 mid 值。

细心的小伙伴会发现,问题 4 的循环条件是 l < r ,当 l == r 时已经跳出了循环,此时 mid 所指正符合arr[mid-1] < arr[mid] < arr[mid+1],因此跳出循环后返回 l 即为所求。


因此,回答文章题目的问题。二分查找一定要有序么?答案是:不一定

在具体问题的分析中,只要能够把问题合理的划分为左、右两侧如何取舍的正确逻辑,那该问题就可以使用“二分”!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

强连通子图

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

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

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

打赏作者

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

抵扣说明:

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

余额充值