二分查找总结

660 阅读2分钟

二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是要求线性表必须有序

一、 查找过程

二分查找步骤1.png 二分查找步骤2.png

  1. 如果目标值等于中间元素,则找到目标值。
  2. 如果目标值较小,继续在左侧搜索。
  3. 如果目标值较大,则继续在右侧搜索。
  4. 重复上面步骤,直到找到,或者遍历结束。

二、核心解题注意点

1. 开闭区间选择不同处理方式不同

这里开闭区间有两种选择:左闭右闭[]和左闭右开[)

// 左闭右闭型
function fn(arr, target) {
    let left = 0;
    let right = arr.length - 1; // ①
    while (left <= right) { // ②
        let middle = Math.floor((left + right) / 2);
        if (target > arr[middle]) {
            left = middle + 1; // ③
        } else if (target < arr[middle]){
            right = middle - 1;
        } else {
            return middle;
        }
    }
    return -1;
}

// 左闭右开型
function fn(arr, target) {
    let left = 0;
    let right = arr.length; // ①
    while (left < right) { // ②
        let middle = Math.floor((left + right) / 2);
        if (target > arr[middle]) {
            left = middle + 1;
        } else if (target < arr[middle]){
            right = middle; // ③
        } else {
            return middle;
        }
    }
    return -1;
}

这个区间的选择要非常注意(上面标注的①②③是几个不同的地方),不然很容易出错。

出现以上情况的说明
②:这个是导致出现①③的原因
①:因为判断条件只有小于,会有这种情况需要注意:当查找的target是最后一个;这是经过多次查找,left到达倒是第二位,这时获取到的中间值就是倒是第二个,肯定小于target,没找到,循环结束。只要给right设置为数组长度就可以解决这个问题,因为最后一位无论怎样都会被middle获取到。
③:这个是由于②和Math.floor导致的,会有这种情况:当剩下未查找刚好是leftmiddleright并且left刚好就是target的时候,如果还是移动右指针到middle-1就会导致循环结束查找失败。

左闭右闭明显比左闭右开简单明了。采用左闭右开需要特别注意3处不同的地方,多练习练习,掌握了其中的原因就好了。

三、几道常见题目

1. 数组中查找某个值

从有序数组中查找某个值,找到返回坐标,找不到返回-1

左闭右闭处理

function fn(arr, target) {
    let leftIndex = 0;
    let rightIndex = arr.length - 1;
    let middle;
    while (leftIndex <= rightIndex){
        middle = Math.floor((leftIndex + rightIndex) / 2);
        // 这里中间值还可以如下获取
        // middle = (leftIndex + rightIndex) >> 1;
        if(target === arr[middle]){
            return middle
        }
        if(target < arr[middle]){
            rightIndex = middle - 1
        }else{
            leftIndex = middle + 1
        }
    }
    return -1
}
console.log(fn([8, 11], 9))

左开右闭处理

function fn(arr, target) {
    let leftIndex = 0;
    let rightIndex = arr.length;
    let middle;
    while (leftIndex < rightIndex){
        // 有符号右移,这个使用的是二进制右移,推出最右边的一个,刚好等于Math.floor()
        middle = (leftIndex + rightIndex) >> 1;
        if(target === arr[middle]){
            return middle
        }
        if(target < arr[middle]){
            rightIndex = middle
        }else{
            leftIndex = middle + 1
        }
    }
    return -1
}
console.log(fn([8, 11], 11))

这里还需要注意一下middle的取值,别让他在rightIndex=middle的时候取值等于rightIndex,这里使用无符号位移或math.floor只会等于leftIndex,都符合。

2. 找到第一个符合要求的值

从有序数组中找到某个值,如果有多个,返回最左边的坐标。找不到返回-1 例如查找[8,8,9]中的8时应该返回0而不是1

解题思路

1.这时没有等于中间值直接返回。
2.大于时候右边找,小于时候左边找,等于时候左边以及中间找(有可能等于的这个就只有一个)

左闭合右闭解法
function fn(arr, target) {
    let leftIndex = 0;
    let rightIndex = arr.length - 1;

    while (leftIndex <= rightIndex){
        let middle = (leftIndex+rightIndex) >> 1;
        if(arr[middle] >= target){
            rightIndex = middle
        }else if(arr[middle] < target){
            leftIndex = middle + 1
        }
        if(leftIndex === rightIndex){
            return leftIndex
        }
    }
    return leftIndex
}


console.log(fn([1,2,3],3))
左闭右开解法
function fn(arr, target) {
    let leftIndex = 0;
    let rightIndex = arr.length;

    while (leftIndex < rightIndex){
        let middle = (leftIndex+rightIndex) >> 1;
        if(arr[middle] >= target){
            rightIndex = middle
        }else if(arr[middle] < target){
            leftIndex = middle + 1
        }
    }
    return leftIndex
}

console.log(fn([1,2,3,4,4,5],2))

这里有几个点要注意:
1.因为有rightIndex = middle,所以使用Math.floor或者>>
2.这个二分查找是查找了所有该查找的,最后leftIndex就是查找值的位置。 3.如果使用的是右闭方法(右索引为最后一位),那么循环里面需要后面加上相等判断,停止循环(全部都查了就出结果了)

3. 数组中查找或插入某个数

给定有序数组arr和目标值target,如果数组中存在target,返回坐标,如果不存在,将target按照数组顺序插入数组,返回插入位置。

解题思路

1.这个只需要在上面的基础上,当判断有值时候返回。
2.大于中间值右边找,小于左边找。
3.最后的左值(右值)就是需要插入的位置。
4.实际上就是二分查找一个值得最坏情况。找到leftIndex===rightIndex。

function fn(arr, target) {
    let leftIndex = 0;
    let rightIndex = arr.length - 1;
    let middle;
    while (leftIndex <= rightIndex){
        middle = Math.floor((leftIndex + rightIndex) / 2);
        if(target === arr[middle]){
            return middle
        }
        if(target > arr[middle]){
            leftIndex = middle + 1
        }else{
            rightIndex = middle - 1
        }
    }
    arr.splice(leftIndex, 0, target);
    return leftIndex
}
console.log(fn([8], 7))

四、总结

  1. 有序数组,查找位置可以用二分查找。
  2. 根据自己选择右边是开区间还是闭区间(闭区间包含)调整while判断条件和if判断处理。
  3. 如果存在leftIndex=middle这种直接赋值的需要注意Math.ceilMath.floor的选择,别让等号两边存在相等。