二分查找(详解)

news2024/11/23 13:21:43

目录

介绍

 思路

循环实现

详解

递归实现1

详解 

注意 

递归实现2 

两个递归代码之间的区别 

总结 

 


介绍

二分查找法,也称为折半查找法,是一种在有序数组中查找特定元素的高效算法。其基本思路是将目标元素与数组中间的元素进行比较,从而可以确定目标元素可能在数组的哪一半,然后逐步缩小搜索范围,直到找到目标元素或确定其不存在为止。

 思路

  1. 确定搜索范围: 首先,确定整个有序数组的搜索范围,即左边界和右边界。通常初始时左边界为数组的第一个元素索引,右边界为数组的最后一个元素索引。

  2. 计算中间元素: 计算左边界和右边界的中间索引,可以使用 (left + right) / 2 进行计算。这个中间元素将用于与目标元素进行比较。

  3. 比较与目标元素: 将目标元素与中间元素进行比较。如果目标元素等于中间元素,则找到了目标,返回中间元素的索引。如果目标元素小于中间元素,则说明目标可能在左半边,更新右边界为中间元素的前一个索引。如果目标元素大于中间元素,则说明目标可能在右半边,更新左边界为中间元素的后一个索引。

  4. 缩小搜索范围: 根据上一步的比较结果,缩小搜索范围。如果目标在左半边,就在左半边继续进行二分查找;如果目标在右半边,就在右半边继续进行二分查找。重复这个过程,不断缩小搜索范围,直到找到目标元素或搜索范围为空。

  5. 重复步骤: 重复执行步骤 2 到步骤 4,直到找到目标元素或搜索范围为空。如果搜索范围为空,说明目标元素不存在于数组中。

image-20220906114801068

二分查找法的关键之处在于每一步都将搜索范围减半,因此它的时间复杂度为 O(log n),其中 n 是数组中元素的数量。相较于线性搜索的 O(n) 时间复杂度,二分查找法在大型有序数组中能够显著提高搜索效率。

然而,二分查找法有一定的前提条件,即数组必须是有序的。如果数组无序,需要先进行排序操作,这会增加额外的时间复杂度。另外,在特定情况下,二分查找法可能不如其他算法高效,例如对于小规模数据或者频繁插入/删除元素的数据结构。

循环实现

from typing import List
class Solution:
  def search(self, nums: List[int], target: int) -> int:
    left = 0
    right = len(nums)-1
    while left <= right:
      # 计算出中位索引
      mid = (right-left)//2 + left
      num = nums[mid]
      if num == target:
        return mid
      elif num > target:
         right = mid - 1
      else:
        left = mid + 1
    return -1

详解

  1. 首先,我们通过 leftright 两个指针来表示当前搜索范围的左右边界。初始时,left 指向数组的第一个元素索引,right 指向数组的最后一个元素索引。

  2. while 循环中,我们首先计算出当前搜索范围的中间索引 mid。这是通过 (right - left) // 2 + left 计算得出的。这个中间索引对应的元素 num 就是我们要和目标元素进行比较的值。

  3. 接下来,我们将 num 与目标元素 target 进行比较。有三种情况:

    • 如果 num 等于 target,则说明我们已经找到目标元素,可以返回 mid
    • 如果 num 大于 target,说明目标元素可能在当前中间元素的左边,所以我们将 right 更新为 mid - 1,缩小搜索范围到左半部分。
    • 如果 num 小于 target,说明目标元素可能在当前中间元素的右边,所以我们将 left 更新为 mid + 1,缩小搜索范围到右半部分。
  4. 循环会继续执行,不断更新 leftright,直到 left 大于 right,即搜索范围为空,或者直到找到目标元素为止。

  5. 如果循环结束时仍未找到目标元素,那么我们返回 -1,表示目标元素不存在于数组中。

递归实现1

def search(nums, target: int) -> int:
  left = 0
  right = len(nums)-1
  if nums !=[]:
    mid = (left + right)//2
    if target > nums[mid]:
      return search(nums[mid+1:],target)
    elif target < nums[mid]:
      return search(nums[:mid],target)
    else:
      return 1
  else:
    return -1

详解 

  1. 首先,我们通过 leftright 两个指针来表示当前搜索范围的左右边界。初始时,left 指向数组的第一个元素索引,right 指向数组的最后一个元素索引。

  2. 然后,通过判断 nums 是否为空数组来决定是否进行递归。如果 nums 不为空,我们进入递归的判断过程。

  3. 在递归判断中,我们首先计算出当前搜索范围的中间索引 mid,通过 (left + right) // 2 计算得出。注意,这里没有加上 left,因为我们将对子数组进行递归,所以 mid 是相对于子数组的索引。

  4. 接下来,我们将 nums[mid] 与目标元素 target 进行比较。有三种情况:

    • 如果 target 大于 nums[mid],则说明目标元素可能在当前中间元素的右边,所以我们对 nums[mid+1:] 进行递归查找,返回递归结果。
    • 如果 target 小于 nums[mid],则说明目标元素可能在当前中间元素的左边,所以我们对 nums[:mid] 进行递归查找,返回递归结果。
    • 如果 target 等于 nums[mid],则说明我们已经找到目标元素,返回 1
  5. 如果数组为空(即 nums 为空),那么直接返回 -1,表示目标元素不存在于数组中。

注意 

递归的结束条件是 nums 为空,或者在递归过程中找到目标元素。这种递归实现在思想上与循环实现类似,不过它将搜索过程拆分为递归的子问题,每次通过截取子数组来缩小搜索范围

递归实现2 

def search2(nums, target, left, right):
  if left <= right:
    mid = (left + right) // 2
    if target > nums[mid]:
      left = mid + 1
      return search2(nums, target, left, right)
    elif target < nums[mid]:
      right = mid - 1
      return search2(nums, target, left, right)
    else:
      return mid
  else:
    return -1

两个递归代码之间的区别 

  1. 函数签名:

    • 第一个代码片段中,函数 search 只接受 numstarget 作为参数,并在函数内部初始化 leftright
    • 第二个代码片段中,函数 search2 接受 numstargetleftright 四个参数,这些参数直接影响递归调用时的搜索范围。
  2. 返回值:

    • 第一个代码片段在找到目标元素时返回了固定的值 1,而应该返回 mid 作为目标元素在数组中的索引。
    • 第二个代码片段在找到目标元素时返回了 mid,正确地表示了目标元素在数组中的索引。
  3. 递归调用:

    • 第一个代码片段在递归调用时通过切片来截取子数组,这会产生额外的空间和时间开销。
    • 第二个代码片段通过在递归调用时更新 leftright 来缩小搜索范围,避免了切片操作,从而提高了效率。

总结 

第二个递归代码更接近标准的二分查找递归实现,正确返回目标元素的索引,而且在递归调用时通过更新参数来实现搜索范围的调整,避免了切片操作,从而更加高效。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/859980.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

跳跃游戏 II——力扣45

文章目录 题目描述解法一 贪心题目描述 解法一 贪心 int jump(vector<int>& nums){in

【剑指offer】栈与队列4题 全刷(详解)

目录 目录 目录 [简单]剑指 Offer 09. 用两个栈实现队列 题目 方法 [简单]剑指 Offer 30. 包含min函数的栈 题目 方法1&#xff1a;笨办法 方法2&#xff1a;辅助栈 [困难]剑指 Offer 59 - I. 滑动窗口的最大值 题目 方法&#xff1a;单调队列 [中等]剑指 Offer 5…

固态硬盘对游戏性能的影响及优势解析

固态硬盘的作用在于提高电脑的读取速度&#xff0c;这对于游戏性能的提升有着重要的影响。在一台电脑中&#xff0c;CPU和显卡是核心硬件&#xff0c;而游戏的流畅度则主要取决于显卡的性能&#xff0c;显卡的性能直接影响游戏的帧数高低。如果我们的电脑配置与朋友的电脑相似&…

异常支出的真实成本意想不到!管理采购异常支出有实招

采购中的异常支出是指合同外支出&#xff0c;或在预先制定的采购政策之外从非首选供应商处购买的支出。不同组织中异常支出的差异会很大&#xff0c;异常支出的比例取决于管理下的支出、采购政策实施的成功程度和采购成熟度。 异常支出会给企业带来多少损失&#xff1f; 曾有…

springboot+mybatis实现简单的增、删、查、改

这篇文章主要针对java初学者&#xff0c;详细介绍怎么创建一个基本的springboot项目来对数据库进行crud操作。 目录 第一步&#xff1a;准备数据库 第二步&#xff1a;创建springboot项目 方法1&#xff1a;通过spring官网的spring initilizer创建springboot项目 方法2&am…

qt creater运行按钮灰色,问题记录

第一次安装还没运行就出了三个错误&#xff1a; 1.F:\wei\Qt\Tools\CMake_64\share\cmake-3.24\Modules\CMakeTestCXXCompiler.cmake:62: error: The C compiler "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/BIN/amd64/cl.exe" is not able to compil…

列队 Queue 接口概述

在Java中&#xff0c;Queue&#xff08;队列&#xff09;是一种基本的数据结构&#xff0c;用于按照先进先出&#xff08;FIFO&#xff09;的顺序存储元素。Java提供了多种实现Queue接口的类&#xff0c;以下是几种常见的实现方式&#xff1a; LinkedList&#xff1a;LinkedLis…

linux环形缓冲区kfifo实践4:异步通知fasync

基础知识 异步通知在内核中使用struct fasync_struct数据结构来描述。 <include/linux/fs.h> struct fasync_struct {spinlock_t fa_lock;int magic;int fa_fd;struct fasync_struct *fa_next; /* singly linked list */struct file *fa_file;struct rcu_head fa…

CTF竞赛密码学之 LFSR

概述: 线性反馈移位寄存器&#xff08;LFSR&#xff09;归属于移位寄存器&#xff08;FSR&#xff09;,除此之外还有非线性移位寄存器&#xff08;NFSR&#xff09;。移位寄存器是流密码产生密钥流的一个主要组成部分。 G F ( 2 ) GF(2) GF(2)上一个n级反馈移位寄存器由n个二元…

matlab使用教程(12)—随机数种子和随机数流

1.生成可重复的随机数 1.1指定种子 本示例显示如何通过首先指定种子来重复生成随机数数组。每次使用相同种子初始化生成器时&#xff0c;始终都可以获得相同的结果。首先&#xff0c;初始化随机数生成器&#xff0c;以使本示例中的结果具备可重复性。 rng( default ); 现在…

django实现登录和登录的鉴权

1、创建数据库的管理员表 在models.py 中定义admin表&#xff0c;为了简单&#xff0c;表里只有用户名和密码还有默认加的id 三个字段 from django.db import models# Create your models here.class Admin(models.Model):username models.CharField(verbose_name"用户…

新利好带动 POSE 持续上扬,月内几近翻倍

PoseiSwap 是 Nautilus Chain 上的首个 DEX&#xff0c;得益于 Nautilus Chain 的模块化 Layer3 构架&#xff0c;PoseiSwap 正在基于 zk-Rollup 方案构建全新的应用层&#xff0c;并基于此构建隐私、合规等全新的特性&#xff0c;为未来其布局 RWA 领域推动 Web2、Web3 世界的…

布谷鸟配音:一站式配音软件

这是一款智能语音合成软件&#xff0c;可以快速将文字转换成语音&#xff0c;拥有多种真人模拟发音&#xff0c;可以选择不同男声、女声、童声&#xff0c;以及四川话、粤语等中文方言和外语配音&#xff0c;并且可对语速、语调、节奏、数字读法、多音字、背景音等进行全方位设…

【gridsample】地平线如何支持gridsample算子

文章目录 1. grid_sample算子功能解析1.1 理论介绍1.2 代码分析1.2.1 x,y取值范围[-1,1]1.2.2 x,y取值范围超出[-1,1] 2. 使用grid_sample算子构建一个网络3. 走PTQ进行模型转换与编译 实操以J5 OE1.1.60对应的docker为例 1. grid_sample算子功能解析 该段主要参考&#xff1a;…

最大子数组和——力扣53

文章目录 题目描述解法一 动态规划题目描述 解法一 动态规划 int maxSubArray(vector<int>& nums){int pre=0, res=nums

spring boot策略模式实用: 告警模块为例

spring boot策略模式实用: 告警模块 0 涉及知识点 策略模式, 模板方法, 代理, 多态, 反射 1 需求概括 场景: 每隔一段时间, 会获取设备运行数据, 如通过温湿度计获取到当前环境温湿度;需求: 对获取回来的进行分析, 超过配置的阈值需要产生对应的告警 2 方案设计 告警的类…

详解双端队列单调队列

1. 双端队列 双端队列&#xff08;Double-ended Queue&#xff09;&#xff0c;简称Deque&#xff0c;是一种具有特殊功能的线性数据结构。它支持从两端进行元素的插入和删除操作&#xff0c;因此可以在队列和栈之间灵活地切换操作。双端队列在编程中经常用于需要在队列和栈之间…

MySQL多表连接查询2

目录 1 所有有门派的人员信息 2 列出所有用户&#xff0c;并显示其机构信息 3 列出不入派的人员 4 所有没人入的门派 5 列出所有人员和门派的对照关系 6 列出所有没入派的人员和没人入的门派 7 求各个门派对应的掌门人名称: ​8 求所有当上掌门人的平均年龄: 9 求所…

6.4 (通俗易懂)可视化详解多通道 多通道输入输出卷积代码实现

以前对多通道和多通道输入输出的卷积操作不理解&#xff0c;今天自己在草稿纸上画图推理了一遍&#xff0c;终于弄懂了。希望能帮助到大家。 多通道可视化 一通道的2x2矩阵 torch.Size([2,2]) 相当于 torch.Size([1,2,2])&#xff0c;是一通道的2x2矩阵 二通道的 2x2矩阵 …

go-zero 是如何实现令牌桶限流的?

原文链接&#xff1a; 上一篇文章介绍了 如何实现计数器限流&#xff1f;主要有两种实现方式&#xff0c;分别是固定窗口和滑动窗口&#xff0c;并且分析了 go-zero 采用固定窗口方式实现的源码。 但是采用固定窗口实现的限流器会有两个问题&#xff1a; 会出现请求量超出限…