4.二分查找

1.二分查找:基于有序序列(递增/递减)的查找算法

核心优势:每次比较后搜索范围可以有效地减半

时间复杂度:O(log n)--最坏情况,其中 n 是要搜索的数组的长度

先举个例子 :我要在1 2 3 4 5 6 7 8 9 10中查询9所在的位置(下标)

令int left=0 right=9(下图我就简化为l和r了)

下标中点mid=(left+right)/2  这里mid还有另一种表达形式 我们晚点再说

[left,right]=[0,9] mid=9/2=4 我们看到下图下标为4的数小于欲查询数9

说明我们需要在[mid+1,right]范围内继续查找 因此令left=mid+1;

[left,right]=[5,9]  因此下标中点mid=7  下标7对应的元素仍然小于9

说明需要在[mid+1,right]范围内继续查找  因此令left=mid+1;

[left,right]=[8,9] 下标中点mid=8对应的数刚好为9  于是返回下标mid=8

大致情况如下:(二分区间为左闭右闭)

代码如下:

我们给的例子不包含重复元素 那么二分查找能不能在有重复元素的有序序列查找呢

当然可以

现在我们来回答之前的问题mid的另一种形式

为什么要有另一种形式呢?

如果二分上界超过int型数据范围的一般时 那么当欲查询元素在序列较靠后的位置时 语句mid=(left+right)/2中的(left+right)就有可能超过int而导致溢出 此时一般使用另一种形式mid=left+(right-left)/2以避免溢出

相信看到这里 有疑问的小猴就该发出疑问了

  1. 在代码中的左闭右闭以及传入的初值必须是上述情况吗

我的回答:当然不是

关于二分区间  我们一般使用左闭右闭和左闭右开 当然也有左开右闭...

但我要告诉你的是:当我们的二分区间改变 代码也需要跟着改变

我们讲一下一般使用的左闭右开区间

在二分区间为左闭右开的情况下 我们传入的初值 while内的条件 以及right的表达式都改变了

现在我们一一解释

因为二分区间为左闭右开 所以原先的left<=right无法在使用 否则会与给定的二分区别相悖

由于右区间为开 所以我们传入的初值right=n 不包含下标为n的元素 下标依旧是从0~ n-1

接下来我们讲right表示式的改变

其实也是因为当right=mid的时候 才能包含下标为mid-1的元素(依旧是右开搞得鬼)

总结:当假设的二分区间不一样时 函数中while循环条件 right left的表达式会有改变

我们只要画张简图就可以一探究竟

讲了这些大家对二分查找应该有所了解了 现在我们趁热打铁 探讨更进一步的问题

如果递增序列arr中的元素可能重复 那么如何对给定的欲查询的元素x  求出序列中第一个大于等于x的元素的位置L以及第一个大于x的元素的位置R  这样元素x在序列中的存在区间就是左闭右开区间[L,R)

例如对下标从0开始、有5个元素的序列{1,3,3,3,6}来说 如果要查询3 则应当得到L=1,R=4 如果查询5 则应当得到L=R=4 如果查询6 则应当得到L=4、R=5;而如果查询8 则应当得到L=R=5 显然如果序列中没有x  那么L和R也可以理解为假设序列中存在x  则x应当在的位置

当我们使用二分查找来解决这个问题时,我们通常会初始化一个左闭右闭的搜索区间,然后根据比较结果逐步缩小这个区间。在每一步中,我们根据中间元素与x的比较结果来决定是向左还是向右搜索。一旦我们找到了L和R,我们就可以将它们转换为左闭右开区间的形式来报告结果。

  1. 我们先来求序列中第一个大于等于x的元素的位置L

假设当前区间为左闭右闭区间[left,right] 那么可以根据mid位置处的元素与欲查询元素x的大小来判断应当往哪个子区间查找

如果arr[mid]>=x 说明第一个大于等于x的元素的位置一定在mid处或在mid的左侧 应往左子区间[left,mid]继续查询 即令right=mid

如果arr[mid]<x 说明第一个大于等于x的元素的位置一定在mid的右侧  应往右子区间[mid+1,right]继续查询 即令left=mid+1

下面给出代码:

这里有几点需要大家注意:

  1. 传入的初值为[0,n]而不是之前的[0,n-1]
  2. 循环条件为left<right而非之前的left<=right

现在我们开始解释

对1来说 二分的初始区间应当能覆盖到所有可能返回的结果 首先二分下界是0 这是必然的(本题来说) 但是二分上界是n-1还是n呢? 考虑到欲查询元素有可能比序列中的所有元素都要大,此时应当返回n(即假设它存在 它应该在的位置) 因此二分上界为n

对2来说 循环条件改为left<right 这是由问题本身决定的 在之前的left<=right那题中 需要当元素不存在时返回-1 这样当left>right时 [left,right]就不再是闭区间 可以此作为元素不存在的判定原则 因此left<=right满足时 循环应当一直执行 但是在本题(返回第一个大于等于x的元素的位置)中,就不需要判断元素x本身是否存在 因为就算它不存在 返回的也是“假设它存在 它应该在的位置” 于是当left==right时  left<right的二分区间[left,right]刚好能夹出唯一的位置---就是需要的结果

这里还需要注意的是 当left==right时 while循环停止 所以最后的返回值既可以是left 也可以是right

2.接下来我们求序列中第一个大于x的元素的位置

其实我们只要将if语句中的>=换成>即可

当我们求出第一个大于等于x的元素的位置L和第一个大于x的元素的位置R后 只需要将其代入左闭右开的区间即可

  • 26
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值