二分查找的最多比较次数

news2024/9/27 19:24:31

答案

对于二分搜索次数最多的问题,计算公式为a < log_2(n) < b,其中a , b , n 均为整数

当顺序表有n个关键字时候,查找失败,至少需要比较a次关键字

查找成功,至少需要b次

举例

已有从小到大排序的10000个数据,用二分查找法检索最多查14次即可得出结论。

当顺序表有n个关键字时:查找失败时,至少比较a次关键字;查找成功时,最多比较关键字次数是b。因为2^13-1=8191,2^14-1=16383,所以13<log2(10000)<14。

疑惑

其实一直都有一个疑惑,二分查找最多几次?对于n个元素,第一次查找会变成n/2 ,第二次查找就会变成n/4,...,第k次查找元素个数就会变成n/(2^k),最坏的情况就是剩下1个元素,那就是他了,于是解方程 \frac{n}{2^k}=1,解得k=log_2(n),log2(n)一般不会是整数,到底应该向下取整?还是应该向上取整?

解答疑惑:log2(n)向下取整+1 就是向上取整了

在这篇文章里面还有另外一种算法

我们对上述发现的规律进行归纳证明:

假设对于区间长度为[2^{(k-1)} ,2^k-1]的区间,最多比较k次
则对于区间长度为[2^k , 2^{(k+1)}-1][2^k, 2^{(k+1)}-1]的区间,假设区间长度为x
如果区间长度为奇数,那么第一次比较以后左右两个区间的长度在2^(k-1) ~ 2^k-1之间,加上第一次比较,最多比较k+1次
如果区间长度为偶数,那么第一次比较以后较大的区间为长度为偶数的区间,此区间的长度仍然在2^(k-1) ~ 2^k-1之间,加上第一次比较,最多比较k+1次

综上对于区间长度为2^(k-1) ~ 2^k-1的区间,最多比较k次(k>=1),即对于区间长度y,最多比较logy+1次

对于标准的二分查找,我们每次从区间[left,right)中取一个值,和中间值mid=(left+right)>>1进行比较,然后将数组分为[left,mid) [mid+1,right),即每次将区间长度x变为(x-1)>>1。最大比较次数显然是我们想要查找的数并不在数组中的时候,这样的话我们需要将区间长度变为0才能结束比较。这样直接分析有些困难,因此我们不妨换一个思路。

如果区间长度为1,显然最多比较1次
区间长度为2,最多比较2次([0,2) -> [0,1) -> [0,0)),log_2(2)=1,可以理解为最多比较log2(2)+1次,也可以理解为,0<log2(2)<2 查找成功需要2次
区间长度为3,最多比较2次([0,3) -> [0,1) [2,3)),可以理解为,log2(3)+1=2次(向下取整),也可以理解为,1<log2(3)<2,查找成功需要2次
区间长度为4,最多比较3次

下标为0,1,2,3

[0,4)开始

->mid=(0+3)/2向下取整=1,[0,1) [2,4)

->mid=(2+3)/2向下取整=2,

->如果不是2,那区间范围里面只剩下一个元素3,在查找一次

可以理解为,log2(4)+1=3次,也可以理解为 1<log2(4)<3,查找成功需要3次。

区间长度为10,3<log2(10)<4,查找成功需要4次比较,log2(10)+1=4次

下标为0,1,2,3,4,5,6,7,8,9

[0,10)开始

->mid=(0+9)/2向下取整=4,[0,4) [5,10)

->mid=(5+9)/2向下取整=7,[5,7) [8,10)

->mid=(5+7)/2向下取整=6,[5] [7]

->再查找一次,才会得到最后的答案

因此我们不难得到规律:
如果最多比较x次,则区间长度为2^(x-1) ~ 2^x-1
对于区间长度y,最多比较logy+1次

那么当n=1000的时候,9 <log2(1000)<10,最大的查找次数是10。 也可以这么理解log2(1000)+1=10,他是向下取整然后再+1

代码细节

每次二分时 mid=(left + right)/ 2 都是向下取整的;
每次比较后,如果没找到,就放弃当前比较的值,right = mid - 1 / left = mid + 1。

二分查找有两种写法,一种是左闭右闭区间,一种是左闭右开区间

 第一种写法就是,左闭右闭区间

if (nums[middle] > target)              right = middle-1;

else if (nums[middle] < target)      left = middle + 1;

else { // nums[middle] == target   return middle; 

对于左闭右闭区间,循环的入口条件是 left <= right 

def binarySearch(list, target):
  lo = 0
  hi = len(list) - 1
  while lo <= hi: #细节1:循环入口条件
    mid = (lo + hi) // 2 #细节2:向下取整
    if list[mid] == target:
      return mid
    #细节3:选取区间,反正就一个主要原则,不能包含,mid这个已经选出来比较过的值
    elif list[mid] < target:
      lo = mid + 1
    else:
      hi = mid - 1
  return None

 注意这是个左闭右闭的闭区间,单纯从数学角度出发,这个区间最短的长度就是1,也就是left=right的时候。如果left>right了,那[left,right]就是空集,这个集合肯定不包含目标值,也就没有继续搜索的必要了,所以循环退出。

 第二种写法就是,左闭右开区间

如果说定义 target 是在一个在左闭右开的区间里,也就是[left, right) ,那么二分法的边界处理方式则截然不同。

有如下两点:

  • while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
  • if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]

 代码复制来源于代码随想录,就是大概看个意思

if (nums[middle] > target)              right = middle

else if (nums[middle] < target)      left = middle + 1;

else { // nums[middle] == target   return middle; 

// 版本二
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
        while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
            int middle = left + ((right - left) >> 1);
            if (nums[middle] > target) {
                right = middle; // target 在左区间,在[left, middle)中
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,在[middle + 1, right)中
            } else { // nums[middle] == target
                return middle; // 数组中找到目标值,直接返回下标
            }
        }
        // 未找到目标值
        return -1;
    }
};

二分法是非常重要的基础算法,为什么很多同学对于二分法都是一看就会,一写就废

其实主要就是对区间的定义没有理解清楚,在循环中没有始终坚持根据查找区间的定义来做边界处理。

Leecode相关题目:

  • 35.搜索插入位置(opens new window)
  • 34.在排序数组中查找元素的第一个和最后一个位置(opens new window)
  • 69.x 的平方根
  • 367.有效的完全平方数

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

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

相关文章

密码框限制xxs注入字符处理

<template><a-form-model-item ref"password" prop"password"><a-input-passwordplaceholder"请输入登录密码"v-model"cusForm.password"/></a-form-model-item> </template><script> export def…

「自控原理」3.2 二阶系统时域分析

本节介绍二阶系统的时域分析&#xff0c;主要介绍欠阻尼情况下的时间响应与动态性能指标 文章目录概述极点的表示方法无阻尼响应临界阻尼响应过阻尼响应欠阻尼响应欠阻尼系统的单位阶跃响应动态性能与极点分布的关系例题改善二阶系统动态性能的措施概述 二阶系统时间响应比较重…

elementUI如何设置input不可编辑

打开一个vue文件&#xff0c;添加一个input标签。如图&#xff1a; 添加disabled设置不可编辑。如图&#xff1a; 保存vue文件后使用浏览器打开&#xff0c;页面上显示的input已经实现不可编辑效果。如图&#xff1a; 参考&#xff1a;elementUI如何设置input不可编辑-百度…

出现死锁的场景分析及解决方法

在上一篇互斥锁的时候最后使用Account.class作为互斥锁&#xff0c;来解决转载问题&#xff0c;所有的账户转账操作都是串行的&#xff0c;性能太差。 我们可以考虑缩小锁定的范围&#xff0c;使用细粒度的锁&#xff0c;来提高并行度。例如用两把锁&#xff0c;转出账本一把&…

Python - 数据容器set(集合)

目录 集合的定义 集合的常用操作 添加新元素 add 移除元素 remove 从集合中随机取出元素 pop 清空集合 clear 取出2个集合的差集 difference 消除2个集合的交集 difference_update 2个集合合并 union for循环遍历 set的实用应用 集合的定义 不支持元素的重复&#…

软件设计师学习笔记-程序设计语言基础知识

前言 备战2023年5月份的软件设计师考试&#xff0c;在此记录学习之路。 知识点总结&#xff0c;具体内容请查看对应的模块。 提示&#xff1a;这里有软件设计师资料&#xff0c;包含软件设计师考试大纲、软件设计师第五版官方教程、历年考试真题。 通过百度网盘分享的文件&am…

好好学习,天天向上——“C”

各位uu们我又来啦&#xff0c;今天小雅兰来给大家分享一个有意思的东西&#xff0c;是为&#xff1a;天天向上的力量 基本问题&#xff1a;持续的价值 一年365天&#xff0c;每天进步1%&#xff0c;累积进步多少呢&#xff1f; 1.01^365 一年365天&#xff0c;每天退步1%&#…

python(运算符,顺序,选择,循环语句)

专栏&#xff1a;python 个人主页&#xff1a;HaiFan. 专栏简介&#xff1a;本专栏主要更新一些python的基础知识&#xff0c;也会实现一些小游戏和通讯录&#xff0c;学时管理系统之类的&#xff0c;有兴趣的朋友可以关注一下。 python基础语法2前言一、输入输出1.通过控制台输…

CSS 伪类

CSS 伪类 CSS 伪类是添加到选择器的关键字&#xff0c;用于指定所选元素的特殊状态。例如&#xff0c;伪类 :hover 可以用于选择一个按钮&#xff0c;当用户的指针悬停在按钮上时&#xff0c;设置此按钮的样式。 举例说明: button:hover {color: blue; }伪类由冒号&#xff…

【应用】SpringBoot -- Webflux + R2DBC 操作 MySQL

SpringBoot -- Webflux R2DBC 操作 MySQLWebflux 概述Webflux 基本使用Webflux R2DBC 操作 MySQLController ServiceRoute Handler参考文章Webflux 概述 简单来说&#xff0c;Webflux 是响应式编程的框架&#xff0c;与其对等的概念是 SpringMVC。两者的不同之处在于 Webf…

贪心策略(四)合并区间合集

目录 435. 无重叠区间移除元素的最小个数 无重叠区间 区间组合结果 合并区间_牛客题霸_牛客网 435. 无重叠区间 移除元素的最小个数 给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, endi] 。返回 需要移除区间的最小数量&#xff0c;使剩余区间互不重…

【Linux调试器-gdb使用】

目录 1. 背景 2. 使用 3 命令总结 1. 背景 通过c语言的学习我们知道程序的发布方式有两种&#xff0c;debug模式和release模式&#xff0c;debug模式就是我们所说的调试模式。我们已经熟悉了在Windows平台下VS系列的调试&#xff0c;接下来我们一起在无图形化界面的Linux下来…

2023-01-18 flink 11.6 时间水印 和 窗口周期的关系计算方法

forBoundedOutOfOrderness 和 TumblingEventTimeWindowsforBoundedOutOfOrderness&#xff08;M&#xff09;TumblingEventTimeWindows&#xff08;N&#xff09;第一条数据的时间TS1第一个窗口期公式&#xff1a;窗口开始时间&#xff1a;win_start ((TS1-M)/N) * N窗口结束时…

Docker上部署goweb项目

文章目录一、安装go语言环境①下载go语言环境②解压go语言环境到指定目录③验证是否成功④配置镜像加速二、go语言项目配置第一种&#xff1a;先编译后打包&#xff08;分步部署&#xff0c;靠谱&#xff09;第二种&#xff1a;直接打包法三、成功运行一、安装go语言环境 ①下…

Zabbix 监控 Linux操作系统的监控指标

一、Zabbix 监控 Linux操作系统的监控指标 (仅供参考) Zabbi x默认使用Zabbix agent监控操作系统,其内置的监控项可以满足系统大部分的指标监控,因此,在完成Zabbix agent的安装后,只需在前端页面配置并关联相应的系统监控模板就可以了。 如果内置监控项不能满足监控需求…

走向开放世界强化学习、IJCAI2022论文精选、机器人 RL 工具、强化学习招聘、《强化学习周刊》第73期...

No.73智源社区强化学习组强化学习周刊订阅《强化学习周刊》已经开启“订阅功能”&#xff0c;扫描下面二维码&#xff0c;进入主页&#xff0c;选择“关注TA”&#xff0c;我们会向您自动推送最新版的《强化学习周刊》。本期贡献者&#xff1a;&#xff08;李明&#xff0c;刘青…

【Kotlin】类的继承 ① ( 使用 open 关键字开启类的继承 | 使用 open 关键字开启方法重写 )

文章目录一、使用 open 关键字开启类的继承二、使用 open 关键字开启方法重写一、使用 open 关键字开启类的继承 Kotlin 中的类 默认都是 封闭的 , 无法被继承 , 如果要想类被继承 , 需要在定义类时 使用 open 关键字 ; 定义一个普通的 Kotlin 类 : class Person(val name: S…

分析GC日志来进行JVM调优

不同的垃圾收集器产生的GC日志大致遵循了同一个规则&#xff0c;只是有些许不同&#xff0c;不过对于G1收集器的GC日志和其他垃圾收集器有较大差别&#xff0c;话不多说&#xff0c;正式进入正文。。。 什么时候会发生垃圾收集 首先我们来看一个问题&#xff0c;那就是什么时…

SpringBoot集成Elasticsearch7.4 实战(三)

本篇文章主要讲的是:在Springboot环境下&#xff0c;管理数据1. 数据管理1.1. 新增数据1.1.1. 单实例新增http方式提交数据&#xff0c;案例中我将数据格式做了规范&#xff0c;提交过程中需要指定索引名&#xff0c;以及JSON格式数据&#xff0c;这样尽可能在实际过程中做到通…

图论算法:普里姆算法(C++实现+图解)

文章目录最小生成树普里姆算法实现过程代码实现最小生成树 什么是最小生成树? 对于如图所示的带权无向连通图来说&#xff1a;从图中任意顶点出发&#xff0c;进行dfs或者bfs便可以访问到图中的所有顶点&#xff0c;因此连通图中一次遍历所经过的边的集合以及图中所有顶点的…