【算法】二分查找详细总结 BinarySearch

二分查找算法的两种实现:

  • 第一种:各类教材、百科的常见写法
int binarySearch(vector<int> &a, int x) {
    int left = 0, right = a.size() - 1, mid;
    while (left <= right) {
        mid = (left + right) / 2;
        if (a[mid] < x) left = mid + 1;
        else if (a[mid] > x) right = mid - 1;
        else return mid;
    }
    return -1;	// 查找失败
}

这种写法,每次循环和 left 与 right 的中值比较,如果 x 大于中间值,往右侧寻找因此 left = mid + 1,若 x 小于中间值则往左侧寻找,right = mid - 1,如果是最坏情况,可能要折半至刚好剩下一个元素才能找到,比如在 1 2 3 4 中查找 4,查找到最后时 leftright 相等,或是考虑数组a只包含一个元素,那 leftright 显然也是相等的,所以循环条件是 left <= right

特点总结,以上算法中:

  1. 在查找成功的情况下,能确定的是 mid 一定是x所在位置(即下标),left 与 right不能确定
  2. 在查找失败的情况下:
    • 先考虑 x 在数组a的最小值和最大值范围之内(但不存在该元素),在查找最后一定会出现只剩下两个元素的情况,那么根据这个二分的算法(小于中值向左找大于中值向右找),x 的值一定会在 a[left] 和 a[right] 之间,于是再根据二分的算法,中值 a[mid]a[left] 相等,a[mid] < x,left = mid + 1,此时 leftright 相等了,也就是说如果查找失败最后被检索的那个数一定是大于x的,那么再进行下一次循环的时候,x小于中值(即 a[left]a[right]right = mid - 1,所以最终 left == right + 1
    • 再考虑x比数组a的最大元素还要大,那么查找过程中 right 是不会动的,直到 left == right,x还是比它两中值大,因此 left = mid + 1,仍然满足 left == right + 1,当x比数组a最小元素还要小时和上面一种情况是一样的,最终检索的元素比x大,right = mid - 1.

其实得到查询失败的后left和right的关系很简单,left <= right 是循环条件,那么当循环结束时,left肯定刚好比right大一。但是现在的主要结论是 如果查找失败,可以得到 a[right] < x < a[left] ,即 right停在了小于x的最大位置上,left停在了大于x的最小位置上 (即使查找的x在数组a的最大最小值范围之外这也在单侧满足),例如:

int a[] = {3, 5, 10, 15, 21, 30, 36, 40};

查找元素 1:left = 0, right = -1, mid = 0
查找元素 45:left = 8, right = 7, mid = 7

现在讨论另一种二分查找的写法以及特点

  • 第二种:出现在使用树状数组动态地查找无序序列中第K小的元素是几。

    int binarySearch(vector<int> &a, int x) {
        int left = 0, right = n - 1, mid;
        while (left < right) {
            mid = (left + right) / 2;
            if (a[mid] >= x) right = mid;
            else left = mid + 1;
        }
        return a[left] == x ? left : -1;	// a[right] == x 也是一样的
    }
    

    这种是在柳婼的题解中看到的写法,与常见的二叉搜索写法不同的是,这里循环条件变成了 left < right ,最大的区别在于,此处即使查找到x也不直接退出,而是等到不满足循环条件后自动退出,选择条件之一变成了 if (a[mid] >= x) right = mid; 但是这里比上面更好分析了,循环结束的条件是 left 正好等于 right,所以可以确定的一点是 不管x是否查找成功,最终 left == right 始终满足,显然在查找成功后 a[left] == a[right] == x,如果查找失败,还是假设最后只剩下两个元素的情况,x在数组最小值和最大值范围内,那么 mid == left, a[mid] < x => left = mid + 1,所以 leftright 最终会停在较大的那一方,所以可以得到的结论:如果查找成功则left与right均在值为x的元素位置上,如果查找失败则left与right均在大于x的最小位置上。但如果给的x大于最大值则停在最后一个元素位置上,另一情况亦然。

虽然这种写法表面看起来多此一举,但是一类问题中,比如能确定所要查询的元素在此范围内,要求如果无此结果则要求返回大于该结果的最小值并做相关的处理,或是另一种,实际上比较的不是x的值,而是通过x在另一序列中得到结果,则这样的写法会很简便,虽然这种方式在查找到结果后没有立刻退出,但是时间复杂度依然是 O(logN),也并没耗去过多时间。

现在将二分查找的相关问题整理明白,而后熟记于心,再碰到与之相关的问题也可免去额外思考的时间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值