普通二分搜索
左侧边界的二分搜索
右侧边界的二分索索
二分搜索实现细节总结
速记卡
let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]我们要用二分搜索查找这个数组中元素 7 的下标,也是就找到下图指针所示元素的下标。
1 普通二分搜索
let binatySearch = function(nums){
let left = 0, right = nums.length - 1
while (left <= right) {
let mid = (left + right) >> 1
if (nums[mid].value == target) {
return mid
}
else if (nums[mid].value < target) {
left = mid + 1
}
else { // nums[mid].value > target
right = mid - 1
}
}
return -1
}本公众号之前有篇文章专门介绍了游标动画,如何看懂冒泡算法的边界条件?
现在游标动画升级了,引入了新的动画元素——线段,我们姑且称新版的动画为线段动画。线段动画具备了部分静态可视化的特征。
图中left和right两个指针表示的区间是[left, right]。
通过线段图, 我们可以很好地理解left和right两个指针的区间变化。
一开始 [left, right] 表示的是整个数组的元素,然后不断取中间值对半缩减区间,因为是有序数组,当中间值大于要查找的元素时,缩减的是右指针,当中间值小于要查找的元素时,缩减的是左指针。
理解了区间的变化,自然也就理解了代码中 mid + 1 以及 mid - 1 的含义。
当然,二分搜索最重要一步就是取左右指针的中间值也是有技巧的, 详情可以看我这篇文章:位运算的妙用-求两个整数的平均值
本文的示例为了简单易懂 (懒), 所以用了最傻瓜的写法。
为了动画更好理解,图中被选为中间值的元素会用其他颜色高亮表示。
请留意下 while (left <= right) 这里为什么是<= ?
我们直接把代码中的等号去掉, 改成 while (left < right) 会有什么效果,直接看动画:
普通二分搜索的弊端
let nums = [1, 2, 3, 4, 4, 6, 6, 6, 6, 10, 11, 12, 13, 14, 15, 16, 16, 16, 16]2 左侧边界二分搜索
// [left, mid) mid [mid+1, right)
let binatySearchLeftBound = function(nums){
let left = 0, right = nums.length
while (left < right) {
let mid = (left + right) >> 1
if (nums[mid].value == target) {
right = mid
}
else if (nums[mid].value < target) {
left = mid + 1
}
else { // data[mid].value > target
right = mid
}
}
if (nums[left].value != target) return -1
return left
}左闭右闭区间实现
// [left, mid-1] mid [mid+1, right]
let binatySearchLeftBoundClosed = function(nums){
let left = 0, right = nums.length - 1
while (left <= right) {
let mid = (left + right) >> 1
if (nums[mid].value == target) {
right = mid - 1
}
else if (nums[mid].value < target) {
left = mid + 1
}
else { // data[mid].value > target
right = mid - 1
}
}
if (nums[left].value != target) return -1
return left
}动画效果如图所示:
可以看到, 这一次代码这里的 while (left <= right) 是有等号的。
这里我们是不是可以总结一个规律一:
如果 [left, right), 那么 while 循环的条件是 while (left < right) ;
如果 [left, right], 那么 while 循环的条件是 while (left <= right) ;
3 右侧边界二分搜索
// [left, mid) mid [mid+1, right)
binatySearchRightBound = function(nums){
let left = 0, right = nums.length
while (left < right) {
let mid = (left + right) >> 1
if (nums[mid].value == target) {
left = mid + 1
}
else if (nums[mid].value < target) {
left = mid + 1
}
else { // data[mid].value > target
right = mid
}
}
if (right <= 0 || nums[right-1].value != target) return -1
return right-1
}动画效果如图所示:
再看看条件 right <= 0 。如果我们要查找数组中不存在的元素, 比如查找0, 动画如下所示。
如果没有 right <= 0 这个条件,那么随着区间右边界的不断缩减, right指针毫无疑问将会超出数组的边界。
左闭右闭区间实现
// [left, mid) mid [mid+1, right)
binatySearchRightBoundClosed = function(nums, node, timeline){
let left = 0, right = nums.length - 1
while (left <= right) {
let mid = (left + right) >> 1
if (nums[mid].value == target) {
left = mid + 1
}
else if (nums[mid].value < target) {
left = mid + 1
}
else { // data[mid].value > target
right = mid - 1
}
}
if (right < 0 || nums[right].value != target) return -1
return right
}3 总结
X References
本文参考了最近github上比较火的项目labuladong / fucking-algorithm里的一篇文章:二分查找详解。
往期回顾
小卡片
规律一:
如果 [left, right), 那么 while 循环的条件是 while (left < right) ;
如果 [left, right], 那么 while 循环的条件是 while (left <= right) ;
字节武装: 用d3动画讲解各种有趣的编程知识。