二分查找

2019-10-10  本文已影响0人  mah93

简介

二分查找(Binary Search)算法,也叫折半查找算法。在给顺序表结构中(也就是数组)快速查找某一个值或者某个区间。二分查找的时间复杂度是O(logn)。虽然二分查找看起来很简单,实现出来的代码不够寥寥十几行,但是就是会出错,要么漏个等号,要么少加1。也就是思路很简单,细节是魔鬼

本文均抄自Leetcode精选解题,本文原作者是labuladong

模版

二分查找的写法基本固定,根据不同的场景修改起始值、判断条件、中止值非常容易忽略细节

function binarySearch(nums, target) {
    let left = 0, 
    let right = ...;

    while(...) {
        let mid = parseInt((right + left) / 2); // 向下取整
        if (nums[mid] == target) {
            ...
        } else if (nums[mid] < target) {
            left = ...
        } else if (nums[mid] > target) {
            right = ...
        }
    }
    return ...;
}

关于mid的取值问题,mid=(right+left)/2是有问题的,因为当right和left比较大的时候,相加的值有可能导致溢出。改进的方法是写成mid = left + (right-left)/2。如果要进行极致的性能优化,可以将除以2的操作写成位运算,left + (right-left) >> 1,相比于除法,计算机计算位运算的速度更快。

本文均写成(right + left) / 2方便阅读,给定的数组均是升序数组

查找某一固定值

在给定的升序数组中,查找该数组中的某一个值,返回该值在数组中所处下标位置,若不存在该值则返回-1

例:给定数组[1,3,4,6,9,11,23,55],查找数字6

function binarySearch(nums, target) {
    let left = 0;
    let right = nums.length-1;

    while(left<=right) {
        let mid = parseInt((right + left) / 2);
        if (nums[mid] == target) {
            return mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid - 1;
        }
    }
    return -1;
}

查找第一个等于给定元素的值(左边界)

类似于上一个例子,现在我们给定的数组变成了[1,6,6,6,9],需要返回第一个等于6的元素的下标。运行上面的函数返回值为2,因为第一个值的mid就等于了6。那么如何改进二分查找,才能找到第一个等于6的元素呢?

function binarySearch(nums, target) {
    let left = 0;
    let right = nums.length;

    while(left<right) {
        let mid = parseInt((right + left) / 2);
        if (nums[mid] == target) {
            right = mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid;
        }
    }
    return left;
}

查找最后一个等于给定元素的值(右边界)

function binarySearch(nums, target) {
    let left = 0;
    let right = nums.length;

    while(left<right) {
        let mid = parseInt((right + left) / 2);
        if (nums[mid] == target) {
            left = mid + 1;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid;
        }
    }
    return left - 1;
}

学习心得

二分查找对于有序数组的查询效率非常高,但是对于边界问题处理上十分棘手,考察细节一不小心就会导致死循环,而且不易查找错误。

有效的理解是确定每次的搜索范围以及循环中止条件。对于每次修改搜索范围的原则是,关注mid有没有被搜索过。在此基础上+1或者-1。

参考资料

上一篇 下一篇

猜你喜欢

热点阅读