算法记录-二分查找的一种实现模板

二分查找模板——基于JavaScript



前言

最近在B站上看到了一个很不错的二分查找模板,利于理解,思路清晰,但是在实际使用的时候似乎碰到了一些问题,在这里做一些简单的总结,来增加这个模板的实用性和通用性。
下面为视频链接
二分查找为什么总是写错?


一、模板讲解

首先我们来看一串代码
// An highlighted block
let left = -1, right = n.length, last = n.length-1, mid;
while(left + 1 != right){
    mid = left + right >> 1;
    if( n[mid] < n[last] ) right = mid;
    if( n[mid] > n[last] ) left = mid;
    else {
        right = last;
        last--;
    }
}
return n[right];

这是《剑指 Offer 》第11题. 旋转数组的最小数字,典型的使用二分查找的题目,在该题中,我使用了该模板解决问题,在过程也碰到了一些问题,这里先不表,后面再进行阐述。
然后我们再看看这套模板的伪代码

# n是一个数组,数组的左边为Blue蓝色区域,右边为Red红色区域

(1)left = -1,  right = n.length,  mid;
#这里定义了三个变量,分别定义了左边界,右边界和中间值

(2)while left + 1 != right
#这里加上循环条件,当两边界逼近至相邻,边界位置确定,我们就可以结束循环了

{
(3)mid = ( left + right ) / 2;
	#此处对mid赋值,这就是二分中间一🔪切了
	
(4)if(mid is Blue) left = mid;
	if(mid is Red) right = mid;
	#这两行代码分别判断了两种情况:
	#1、如果mid是蓝色,就把mid赋值给左边界,也就是左边界推进到了mid,
	蓝色区域大幅逼近
	#1、如果mid是红色,就把mid赋值给右边界,也就是右边界推进到了mid,
	红色区域大幅逼近
}
#以上为循环体的内容,不断循环二分逼近我们的目标终点

(5)return left or right
#当循环达到终止条件,边界就已经确定了,这时我们只需要根据实际情况返回边界左
或者边界右即可。
#举个例子,如果是需要查找第一个为红色的元素,则最后返回right;如果查找的是最
后一个蓝色的元素,则返回left。

在这套模板的使用过程中,left、right和mid的默认值和变化方式都是恒定的,不需要改动,我们需要变动的核心部分是(4)里的条件判断,题目不一定会是让你寻找蓝色或者红色区域,也有可能是根据大小、内容等去做判断,我们需要理解题意,找到这个可以区分两个部分的关键特征值,然后再使用此值与mid做相关的比较判断。
*注意:该特征值不一定是一个静态常量,也有可能是变量,这种情况就需要在循环过程中不断改变特征值


二、使用

1.会遇到的问题

返回值越界
因为在我们初始定义的值中,left为-1,right为n数组的长度,这样做的目的是包裹数组中0~n-1的所有数值,减少特殊情况的遗漏。不过当数组全为红色,却要找出第一个蓝色值的时候,可能就会返回-1这样没有意义的值,也就是发生了返回值的越界,这种情况下我们只需要在(5)中的return之前,加上一行if判断,比较返回值与索引,从而确定其是否为合法输出。
判断条件重合
还是前面的伪代码,我们看到(4)中的判断条件是is Blue or is Red,然而在实际问题中,我们可能会遇到一个既有红色又有蓝色的部分,这个时候两个判断条件都能命中它,可能就会造成错误。所以这种情况下,我们需要在模板中(5)部分的条件判断后面再加上对于特殊情况的判断,以保证在条件重合时或者条件都无法命中时的正确性。
特殊情况讨论
在上一段中讲到了特殊情况的判断,那如果判断出了特殊情况,我们该如何对其进行处理呢?在这种特殊情况下,很多时候不能使用前两个常规的判断处理,而是需要单独进行特殊处理。开头的例子中,我们在特殊情况下,不断缩减原数组的长度,使其成为一个新的模板(包括新的left,right和判断条件),此时我们的判断条件就是动态的,根据实际情况的变化来改变数组模板,保证其正确性。
JS语法的特殊性
这里还有的一些需要注意的点则是语言特性的影响,这里以JavaScript语法的特殊性举例的,在JS中,除法是会得到小数的,那么我们就不能直接写(left+right)/ 2,而是要写成parseInt((left+right)/ 2),注意此时相当于直接截断后面的小数部分,只保留整数部分。

mid = (left + right) / 2  ==parseInt((left+right)/ 2)

2.小技巧

在通过该模板书写代码的时候,我们还有一些使书写过程更加方便,或者使代码更加严谨的小技巧可以使用.
(1)移位运算符

mid = parseInt(left + right) / 2  /*等价于*/   mid = left + right >> 1

在使用移位运算符之前,我们需要先了解它是什么。我们可以通过下面这篇博客了解这个运算符的基本概念:
JS移位运算符

了解之后我们就能明白,上面的>>1相当于数字在二进制的格式下整体向右移动了一位。在十进制下整体向右移动一位,数字就缩小了十倍,二进制下不就是除以二嘛,而且由于超出的值会被直接丢弃,就相当于撇除了小数部分,碰到JS这种直接相除会得到小数的语言,我们就可以省去写parseInt的功夫了。

(2)使用减数避免溢出
想象一下,当我们的数组非常非常大的时候,left+right两数相加很有可能超出整型所规定的最大极限值,导致爆int,也就是溢出,为了使我们的算法更加严谨,并且得到正确性更高的结果,我们可以先将两数相减,再用left加上得到的值,这样就可以用安全性更高的方式得到正确的结果。

mid = (left + right) / 2  /*等价于*/   mid = (left + (right-left)) / 2

总结

一个好的算法模板能够让我们更好的理解和使用这个算法,二分查找这个经典的算法在理解门槛上并不高,许多人都能轻松掌握其背后的思想,但是在实际场景中,我们往往难以从容高效地将其正确书写出来,因为其边界值的判定和循环中的判断条件,乃至于最后的取值,都有许多种方式,一万个人眼中有一万个哈姆雷特,五花八门的实现方式让我们更加容易混淆和迷失,希望这篇博客能够让你更好的使用这个模板,因为即使用了模板,我们也要考虑各种实际情况和特殊场景。最后祝各位早日拿下二分查找!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值