算法之二分查找算法

查找算法之二分查找算法

1、 概述

二分查找算法也称折半查找算法,是在有序数组中用到的较为频繁的一种查找算法。在未接触二分查找算法时,最通用的一种做法是,对数组进行遍历,跟每个元素进行比较,即顺序查找。二分查找较顺序查找更优,因为这种算法每一次比较都使查找范围缩小一半。

二分查找是一种基于比较目标值和数组中间元素的教科书式算法。

  • 如果目标值等于中间元素,则找到目标值。
  • 如果目标值较小,继续在左侧搜索。
  • 如果目标值较大,则继续在右侧搜索。

2、算法思想

二分查找算法是建立在有序数组基础上的。算法思想为:

  • 查找过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则查找过程结束;
  • 如果某一待查找元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟第 1 点一样从中间元素开始继续进行查找。
  • 如果在某一步骤数组为空,则代表找不到。

例子: 有序数组nums[-1,0,3,5,9,12],目标值即要查找的元素 target = 9;

算法实现思路如下:

  • 初始化起点位置和终点位置,声明数组中间位置变量。
var left = 0;   // 起点数组索引
var right = nums.length - 1;  // 终点数组索引
var middle = 0;  // 数组中间位置
  • left <= right

    • 求中间middle索引: middle = Math.floor(left + (right - left) / 2); (建议使用这种方法求中间索引,可以防止 left + right 溢出(超出整数范围))。

    • 比较中间元素 nums[middle] 和目标值 target

      • 如果 target = nums[middle] ,返回 middle
      • 如果 target > nums[middle] ,则在右侧继续搜索,把数组中间位置加1作为下一次计算的起点 left = middle + 1
      • 如果 target < nums[middle] ,则在左侧继续搜索,把数组中间位置减1作为下一次计算的终点 right = middle - 1

在这里插入图片描述

3、算法实现

题目描述:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例2:
输入: nums = [-1, 0, 3, 5, 9, 12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 - 1

方法1:使用非递归方式查找

/*二分查找:非递归方式查找*/
var search = function (nums, target) {
    var left = 0;   // 起点数组索引
    var right = nums.length - 1;  // 终点数组索引
    var middle = 0;  // 数组中间位置
    while (left <= right) {
        middle = Math.floor(left + (right - left) / 2);  // 防止计算时溢出
        if (target > nums[middle]) {
            left = middle + 1;  // 继续在右侧搜索
        } else if (target < nums[middle]) {
            right = middle - 1;  // 继续在左侧搜索
        } else {
            return middle;   // 找到目标值,返回目标值在数组中的索引
        }
    }
    return -1;  // 没有找到目标值
};

var ary1 = [-1, 0, 3, 5, 9, 12];
var target1 = 9;
console.log(search(ary1, target1));  // 4

方法2:使用递归方式查找

/* 二分查找:递归方式查找 */
var search = function (nums, target, left, right) {
    if (left > right) {
        return -1;   // 没有找到目标值
    }
    var middle = Math.floor(left + (right - left) / 2);  // 防止计算时溢出
    
    if (target > nums[middle]) {
        left = middle + 1;
        return search(nums, target, left, right);  // 继续在右侧搜索
    } else if (target < nums[middle]) {
        right = middle - 1;
        return search(nums, target, left, right);  // 继续在左侧搜索
    } else {
        return middle;   // 找到目标值,返回目标值在数组中的索引
    }

};

var ary1 = [-1, 0, 3, 5, 9, 12];
var target1 = 9;
console.log(search(ary1, target1, 0, ary1.length - 1));  // 4

4、复杂度分析

  • 时间复杂度:O(logN)。
  • 空间复杂度:O(1)。

5、案例:搜索插入位置

题目描述:
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

解题思路:
标签:二分查找

  • 如果该题目暴力解决的话需要 O(n) 的时间复杂度,但是如果二分的话则可以降低到 O(logN) 的时间复杂度
  • 整体思路和普通的二分查找几乎没有区别,先设定左侧下标 left 和右侧下标 right,再计算中间下标 mid
  • 每次根据 nums[mid] 和 target 之间的大小进行判断,相等则直接返回下标,nums[mid] < target 则 left 右移,nums[mid] > target 则 right 左移
  • 查找结束如果没有相等值则返回 left,该值为插入位置
  • 时间复杂度:O(logN)

二分查找的思路不难理解,但是边界条件容易出错,比如 循环结束条件中 left 和 right 的关系,更新 left 和 right 位置时要不要加 1 减 1

代码如下:

var searchInsert = function (nums, target) {
    var left = 0;   // 起点数组索引
    var right = nums.length - 1;  // 终点数组索引
    var middle = 0;  // 数组中间位置
    while (left < right) {
        middle = Math.floor(left + (right - left) / 2); // 防止计算时溢出
        if (target > nums[middle]) {
            left = middle + 1;  // 继续在右侧搜索
        } else if (target < nums[middle]) {
            right = middle - 1;  // 继续在左侧搜索
        } else {
            return middle;   // 找到目标值,返回目标值在数组中的索引
        }
    }

    // 此时有 left == right , 目标值不在数组中
    if (target > nums[left]) {
        return left + 1;   // 返回 target 将会被按顺序插入的位置
    } else {
        return left;   // 返回 target 将会被按顺序插入的位置
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值