课程讲解--深入探究二分算法

 

一、二分查找算法的基本概念

  1. 定义与原理
    • 二分查找,也被称为折半查找,是一种在有序数据集合中查找特定元素的高效算法。其原理基于分治思想,每次查找都将查找区间缩小一半。例如,在一个有序数组中查找一个特定的数字,我们先比较数组中间位置的元素与目标元素的大小。如果中间元素等于目标元素,那么查找成功;如果中间元素大于目标元素,那么目标元素必然在数组的前半部分,我们就将查找区间缩小到数组的前半部分,然后继续在这个新的区间内进行同样的操作;如果中间元素小于目标元素,那么目标元素就在数组的后半部分,我们就将查找区间缩小到后半部分再进行查找。这种不断缩小查找区间的方式使得查找速度非常快。引用自[1]“二分查找是一种非常简单易懂的快速查找算法,生活中到处可见。”
  2. 适用场景
    • 二分查找适用于有序的数据结构,比如有序数组。当数据量较大时,它的优势更加明显。例如,在一个包含大量有序用户信息(按照用户ID或者注册时间等排序)的数据库中查找特定用户的信息,使用二分查找可以快速定位到目标用户。如果数据是无序的,那么首先需要对数据进行排序,然后才能使用二分查找。

二、二分查找算法的实现

  1. 循环实现方式
    • 在C++ 中,一个简单的二分查找函数实现如下:
boolbsearch(std::vector<int>&vec, intvalue) { 
    if (vec.empty())  { 
        returnfalse; 
    } 
    intlow = 0, high = vec.size()  - 1; 
    //当前查找的区间 
    while (low <= high) { 
        intmid = low+((high - low)/2); 
        if (vec[mid]==value) { 
            //找到了 
            returntrue; 
        } else if (vec[mid]>value) { 
            low = mid + 1; 
            //更新要查找的区间 
        } else { 
            high = mid - 1; 
            //更新要查找的区间 
        } 
    } 
    returnfalse; 
} 
  • 这里需要注意几个容易出错的地方:
    • 循环退出条件:循环退出条件是low <= high,而不是low < high。如果写成low < high,可能会导致某些情况下无法正确找到目标元素。引用自[1]“注意是low <= high,而不是low < high”。
    • mid的取值:不能简单地写成mid=(low + high)/2。因为如果lowhigh比较大的话,两者之和就有可能会溢出。改进的方法是将mid的计算方式写成low+(high - low)/2。因为相比除法运算来说,计算机处理位运算要快得多。引用自[1]“不能写成,mid=(low + high)/2。因为如果lowhigh比较大的话,两者之和就有可能会溢出。改进的方法是将mid的计算方式写成low+(high - low)/2”。
    • low和high的更新low = mid+1high = mid - 1。注意这里的+1 - 1,如果直接写成low = mid或者high = mid,就可能会发生死循环。引用自[1]“注意这里的+1 - 1,如果直接写成low = mid或者high = mid,就可能会发生死循环”。
  1. 递归实现方式
    • 除了使用循环来实现二分查找,还可以用递归来实现。以下是一个递归实现二分查找的伪代码示例:
function binarySearchRecursive(array, target, low, high) { 
    if (low > high) { 
        return false; 
    } 
    int mid = low+((high - low)/2); 
    if (array[mid]==target) { 
        return true; 
    } else if (array[mid]>target) { 
        return binarySearchRecursive(array, target, low, mid - 1); 
    } else { 
        return binarySearchRecursive(array, target, mid + 1, high); 
    } 
} 
  • 在递归实现中,每次递归调用都会缩小查找区间,直到找到目标元素或者确定目标元素不存在。递归实现的思路更加直观地体现了二分查找的分治思想,但由于递归调用会占用额外的栈空间,在数据量非常大时可能会导致栈溢出。

三、二分查找算法的复杂度分析

  1. 时间复杂度
    • 二分查找的时间复杂度是O(logn)O(logn)。假设数据集合的大小为nn,每次查找都会将查找区间缩小一半。可以将这个过程看作是一个等比数列,当n/2^k = 1n/2k=1(nn经过kk次缩小后变为1)时,kk的值就是总共缩小的次数。而每一次缩小操作只涉及两个数据的大小比较,所以,经过了kk次区间缩小操作,时间复杂度就是O(k)O(k),这里k = log_2nk=log2​n。例如,当n = 2^{32}n=232(大约是42亿)时,如果我们在这么多数据中用二分查找一个数据,最多也只需要比较32次。引用自[1]“二分查找是一种非常高效的查找算法,其时间复杂度达到了惊人的O(logn)O(logn)。分析如下:。可以看出来,这是一个等比数列。其中n/2^k = 1n/2k=1时,kk的值就是总共缩小的次数。而每一次缩小操作只涉及两个数据的大小比较,所以,经过了kk次区间缩小操作,时间复杂度就是O(k)O(k)。”
    • 这种对数时间复杂度是非常高效的,在大规模数据面前表现出色。甚至在某些情况下,比时间复杂度是常量级O(1)O(1)的算法还要高效。因为lognlogn是一个增长非常缓慢的函数,即便nn非常非常大,对应的lognlogn也很小。而且在使用大O标记法表示时间复杂度的时候,会省略掉常数、系数和低阶。对于常量级时间复杂度的算法来说,O(1)O(1)有可能表示一个非常大的常量值,比如O(1000)O(1000)、O(10000)O(10000)。
  2. 空间复杂度
    • 对于循环实现的二分查找,其空间复杂度是O(1)O(1),因为只需要使用几个额外的变量(如lowhighmid等)来记录查找区间和中间位置,这些变量所占用的空间不随数据规模nn的增长而增长。
    • 对于递归实现的二分查找,空间复杂度是O(logn)O(logn)。因为递归调用的最大深度是lognlogn(每次递归都会将查找区间缩小一半),每个递归调用都会占用一定的栈空间,所以总的空间复杂度是O(logn)O(logn)。

四、二分查找算法的变体

  1. 查找第一个等于目标值的元素
    • 在有序数组中可能存在多个等于目标值的元素,有时候我们需要找到第一个等于目标值的元素。以下是一种实现方式:
int findFirstEqual(std::vector<int>&vec, intvalue) { 
    intlow = 0, high = vec.size()  - 1; 
    int result = - 1; 
    while (low <= high) { 
        intmid = low+((high - low)/2); 
        if (vec[mid]==value) { 
            result = mid; 
            high = mid - 1; 
        } else if (vec[mid]>value) { 
            low = mid + 1; 
        } else { 
            high = mid - 1; 
        } 
    } 
    return result; 
} 
  • 在这个实现中,当找到一个等于目标值的元素时,我们不立即返回,而是继续在当前元素的左边查找,看是否还有等于目标值的元素,直到确定当前元素就是第一个等于目标值的元素。
  1. 查找最后一个等于目标值的元素
    • 类似地,要找到最后一个等于目标值的元素,可以使用以下实现方式:
int findLastEqual(std::vector<int>&vec, intvalue) { 
    intlow = 0, high = vec.size()  - 1; 
    int result = - 1; 
    while (low <= high) { 
        intmid = low+((high - low)/2); 
        if (vec[mid]==value) { 
            result = mid; 
            low = mid + 1; 
        } else if (vec[mid]>value) { 
            low = mid + 1; 
        } else { 
            high = mid - 1; 
        } 
    } 
    return result; 
} 
  • 这里当找到等于目标值的元素时,继续在当前元素的右边查找,确定是否还有等于目标值的元素,直到找到最后一个等于目标值的元素。
  1. 查找第一个大于目标值的元素
    • 实现如下:
int findFirstGreater(std::vector<int>&vec, intvalue) { 
    intlow = 0, high = vec.size()  - 1; 
    int result = - 1; 
    while (low <= high) { 
        intmid = low+((high - low)/2); 
        if (vec[mid]>value) { 
            result = mid; 
            high = mid - 1; 
        } else { 
            low = mid + 1; 
        } 
    } 
    return result; 
} 
  • 当找到一个大于目标值的元素时,记录下来并继续在左边查找是否还有更小的大于目标值的元素。
  1. 查找最后一个小于目标值的元素
    • 实现如下:
int findLastLess(std::vector<int>&vec, intvalue) { 
    intlow = 0, high = vec.size()  - 1; 
    int result = - 1; 
    while (low <= high) { 
        intmid = low+((high - low)/2); 
        if (vec[mid]<value) { 
            result = mid; 
            low = mid + 1; 
        } else { 
            high = mid - 1; 
        } 
    } 
    return result; 
} 
  • 当找到一个小于目标值的元素时,记录下来并继续在右边查找是否还有更大的小于目标值的元素。

五、二分查找与其他算法的比较

  1. 与线性查找的比较
    • 线性查找:线性查找是一种最基本的查找算法,它从数据集合的第一个元素开始,逐个比较元素与目标元素,直到找到目标元素或者遍历完整个数据集合。线性查找的时间复杂度是O(n)O(n),其中nn是数据集合的大小。例如,在一个未排序的数组中查找一个元素就只能使用线性查找。
    • 二分查找优势:与线性查找相比,二分查找的时间复杂度O(logn)O(logn)远远优于线性查找的O(n)O(n)。当数据量较大时,这种优势更加明显。例如,当n = 1000000n=1000000时,线性查找在最坏情况下需要比较1000000次,而二分查找最多只需要比较20次(log_21000000\approx20log2​1000000≈20)。然而,二分查找要求数据必须是有序的,而线性查找对数据的顺序没有要求。如果数据本身是无序的,并且不需要频繁查找,那么线性查找可能是一个更简单、更直接的选择。
  2. 与哈希表查找的比较
    • 哈希表查找:哈希表是一种根据关键码值(Key - value)而直接进行访问的数据结构。通过哈希函数将键映射到一个桶(bucket)中,理想情况下,哈希表查找的时间复杂度可以接近O(1)O(1)。例如,在一个存储用户信息的哈希表中,根据用户ID查找用户信息,只需要通过哈希函数计算出桶的位置,就可以直接获取到对应的信息。
    • 二分查找优势与劣势:二分查找的时间复杂度是O(logn)O(logn),虽然也是高效的,但相比哈希表查找的近似O(1)O(1)还是稍慢。然而,哈希表需要额外的空间来存储哈希函数、桶等信息,并且哈希函数的设计需要考虑避免哈希冲突等问题。而二分查找只需要在有序数组上进行操作,不需要额外的空间来存储复杂的结构。另外,二分查找适用于有序的静态数据,而哈希表更适合于动态数据的快速插入、删除和查找操作。

六、二分查找在实际中的应用

  1. 在数据库查询中的应用
    • 在数据库管理系统中,当对有序的索引列进行查询时,二分查找的思想常常被用到。例如,在一个按照用户年龄排序的用户表索引中查找特定年龄的用户,数据库可以利用二分查找快速定位到可能包含目标用户的索引块,然后在这个块内进一步查找具体的用户记录。这样可以大大提高查询的效率,减少磁盘I/O操作的次数。
  2. 在算法竞赛中的应用
    • 在算法竞赛中,二分查找及其变体经常被用来解决各种类型的问题。比如,在一些需要在给定区间内找到满足某种条件的最优解的问题中,可以将问题转化为二分查找的形式。例如,给定一个函数f(x)f(x),它在某个区间[a,b][a,b]上是单调递增或者单调递减的,要求找到使得f(x)=kf(x)=k的xx的值,就可以使用二分查找来解决。先确定一个初始的查找区间,然后根据函数值与目标值kk的比较结果不断缩小查找区间,直到找到满足条件的xx。
  3. 在计算机图形学中的应用
    • 在计算机图形学中,二分查找可以用于一些图形渲染和几何计算方面的优化。例如,在对一个复杂的三维模型进行光线追踪时,需要确定光线与模型表面的交点。如果对模型的表面三角形进行某种排序(比如按照距离光源的远近排序),就可以使用二分查找来快速定位可能与光线相交的三角形区域,从而减少不必要的计算,提高渲染的效率。

七、二分查找的局限性

  1. 数据必须有序
    • 二分查找的一个最大局限性就是要求数据必须是有序的。如果数据是无序的,就需要先对数据进行排序操作,而排序操作本身可能会消耗大量的时间和空间。例如,对于一个包含nn个元素的数组,如果使用快速排序算法进行排序,其平均时间复杂度是O(nlogn)O(nlogn),这在某些实时性要求较高的场景下可能是不可接受的。
  2. 不适合动态数据结构
    • 二分查找通常是在数组这种静态数据结构上进行操作的。对于动态数据结构,如链表,由于无法直接通过下标快速访问中间元素,二分查找就不适用了。在链表中查找一个元素,通常只能使用线性查找,从链表的头节点开始逐个节点进行查找。即使链表中的数据是有序的,也很难实现像在数组中那样高效的二分查找操作。
  3. 数据量较小时优势不明显
    • 当数据量较小时,二分查找的O(logn)O(logn)时间复杂度相对于线性查找的O(n)O(n)时间复杂度的优势并不十分明显。例如,当n = 10n=10时,线性查找最多需要比较10次,而二分查找最多需要比较log_210\approx3.32log2​10≈3.32,向上取整为4次。在这种情况下,由于二分查找的实现相对复杂一些,可能线性查找会是一个更简单、更直接的选择。

八、总结

  1. 重要性
    • 二分查找作为一种经典的查找算法,在计算机科学领域有着广泛的应用。它的高效性(O(logn)O(logn)的时间复杂度)使得它在处理大规模有序数据时能够快速定位目标元素。无论是在数据库查询、算法竞赛,还是在计算机图形学等领域,二分查找都发挥着重要的作用。
  2. 发展与改进方向
    • 随着计算机技术的不断发展,对于二分查找算法的研究也在不断深入。一方面,对于二分查找算法的变体的研究,如如何更高效地实现各种变体(查找第一个等于、最后一个等于、第一个大于、最后一个小于目标值的元素等),以满足不同的实际需求。另一方面,在面对新兴的数据类型和存储结构时,如何将二分查找的思想进行扩展和应用也是一个研究方向。例如,在分布式存储系统中,如何利用二分查找来提高数据查找的效率等。同时,如何更好地结合其他算法和数据结构,发挥二分查找的最大优势也是未来研究的一个方向。

九、感想

假如你真的听懂了,那么,就做做这些题吧

二分查找题目专栏-东方博宜OJ

拜拜┏(^0^)┛

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

达梦数据库DM Exception字符串截断错误,略坑~

前言 我之前在使用达梦数据库的时候&#xff0c;遇到了很多很多的问题&#xff0c;主要对达梦数据库也不是很熟悉&#xff0c;它的语法和我所熟悉的mysql和postgresql有很大的区别。 今天&#xff0c;讲一下我之前遇到的一个问题。这个问题的起因是用达梦数据库迁移工具&…

Java版工程行业管理系统-提升工程项目的综合管理能力

工程项目管理涉及众多环节和角色&#xff0c;如何实现高效协同和信息共享是关键。本文将介绍一个采用先进技术框架的Java版工程项目管理系统&#xff0c;该系统支持前后端分离&#xff0c;功能全面&#xff0c;可满足不同角色的需求。从项目进度图表到施工地图&#xff0c;再到…

高考:心态、时间、知识,多维度攻略让你脱颖而出

高考&#xff0c;宛如一场无声的激战&#xff0c;承载着无数莘莘学子的梦想与热望。在这激烈的竞争中&#xff0c;充分且周全的准备显得尤为关键。那么&#xff0c;高考备考究竟应从哪些方面入手&#xff1f;又有哪些行之有效的备考策略能为我们保驾护航呢&#xff1f; 一、高考…

信息安全工程师(82)操作系统安全概述

一、操作系统安全的概念 操作系统安全是指操作系统在基本功能的基础上增加了安全机制与措施&#xff0c;从而满足安全策略要求&#xff0c;具有相应的安全功能&#xff0c;并符合特定的安全标准。在一定约束条件下&#xff0c;操作系统安全能够抵御常见的网络安全威胁&#xff…

SQL server 中 CROSS APPLY的使用

CROSS APPLY 是 SQL Server 中的一个操作符&#xff0c;用于将一个表表达式&#xff08;如子查询、函数等&#xff09;与外部表进行连接。CROSS APPLY 类似于 INNER JOIN&#xff0c;但它允许你在一个查询中多次引用外部表的行&#xff0c;并且可以动态地生成结果集。 基本语法…

硬件---3电容---电容特性、上电和断电延时、稳压功能、容抗计算

一电容是什么 1概念 电容就是两块不连接的导体加上中间的绝缘材料。其本身能够存储电荷&#xff0c;当在这两个互相导体两端增加电压的时候&#xff0c;就会形成电场&#xff0c;从而存储电能。 2注意 <1>电解电容正负极一定不能接反&#xff0c;如果接反轻则烧坏&am…

行车记录打不开?原因分析与数据恢复全攻略

行车记录遭遇困境 行车记录仪&#xff0c;作为现代驾驶中的重要设备&#xff0c;不仅能够帮助我们记录行车过程&#xff0c;还能在关键时刻提供有力的证据。然而&#xff0c;当行车记录突然打不开时&#xff0c;这无疑给车主们带来了不小的困扰。行车记录打不开&#xff0c;可…

考研要求掌握C语言(归并排序)

归并排序考啥&#xff1f; 在考研中归并排序只出在选择题&#xff0c;理解原理很重要 且在考研中考靓靓归并&#xff0c;还是比较简单的 归并排序原理 就是每次分一半&#xff0c;直到每一半只含有一个或不能再分时&#xff0c;一半一半的进行排序&#xff0c;最终合并两个…

编译工具与文件学习(一)-YAML、repos、vcstoolcolcon

YAML YAML&#xff08;YAML Ain’t Markup Language&#xff09;是一种人类可读的数据序列化格式&#xff0c;常用于配置文件、数据交换和存储结构化数据。YAML 的设计目标是简洁、易读&#xff0c;并且能够表示复杂的数据结构。 YAML 文件的基本语法 基本结构&#xff1a; Y…

HDFS和HBase跨集群数据迁移 源码

HDFS集群间数据迁移&#xff08;hadoop distcp&#xff09; hadoop distcp \ -pb \ hdfs://XX.14.36.205:8020/user/hive/warehouse/dp_fk_tmp.db/ph_cash_order \ hdfs://XX.18.32.21:8020/user/hive/warehouse/dp_fksx_mart.db/HBase集群间数据&#xff08;hbase ExportSnap…

ffplay 实现视频流中音频的延迟

ffplay -rtsp_transport tcp -i rtsp://admin:1234qwer192.168.1.64:554/Streaming/Channels/101 -vn -af "adelay5000|5000"在这个命令中&#xff1a; -vn 参数表示只播放音频。 -af "adelay5000|5000" 参数表示将音频延迟5000毫秒&#xff08;即5秒&…

(十五)JavaWeb后端开发——异常处理/AOP面向切面编程

目录 1.异常处理 2.AOP概述 3.AOP核心概念 4.AOP-通知类型 5.切入点表达式 6.连接点 1.异常处理 Web后端开发的三层架构是Controller调用Service&#xff0c;Service调用Mapper&#xff0c;如果碰到了异常就会逐级向上抛出&#xff0c;所以Java就在Controller层设计了全…

Linux命令 - linux索引节点、硬链接、软链接的介绍与使用

文章目录 1 索引节点inode2 硬链接Hard Link3 软链接Soft Link 1 索引节点inode 在Linux系统中&#xff0c;保存在磁盘分区中的文件&#xff0c;不管是什么类型&#xff0c;系统都会给它分配一个编号&#xff0c;这个编号被称为索引节点编号&#xff08;Inode Index&#xff0…

浅谈智能家居在智慧养老实训室中的作用

随着人口老龄化的加剧&#xff0c;智慧养老逐渐成为社会关注的热点。在此背景下&#xff0c;智能家居技术以其独特的优势受到广泛关注。智能家居不再是奢侈品&#xff0c;而是提升老年人生活品质和家庭养老效率的有效工具。它们为老年人提供了便捷、安全、舒适的生活环境&#…

【DL】YOLO11 OBB目标检测 | 模型训练 | 推理

本文进行YOLO11的旋转目标检测任务,旋转目标检测能够更精确地定位和描述那些非水平排列的目标,比如倾斜的飞机、船舶等。在原始的目标检测中,添加一个角度预测,实现定向边界框检测。 话不多说,先来个效果图!!! YOLO11中的旋转目标检测的特点 ▲更精确的定位:通过使用…

Linux Centos7 如何安装图形化界面

如果系统是以最小安装的话,一般是不带有图形化界面的,如果需要图形话界面,需要单独安装。本篇教程,主要介绍如何在CentOS7中安装图形化界面。 1、更新系统 首先,保证系统依赖版本处于最新。 sudo yum update -y2、安装 GNOME 桌面环境 sudo yum groupinstall "GNOME…

学习笔记:黑马程序员JavaWeb开发教程(2024.11.8)

5.10 分层解耦-分层解耦&#xff08;IOC-DI&#xff09; 在之前写的代码中&#xff0c;Controller层中new了一个Service层中的对象&#xff0c;在Service层中类名改变&#xff0c;则Controller层中也需要变化&#xff0c;这就是两个层之中耦合较重&#xff0c;需要减少耦…

数据库的使用02:SQLServer的连接字符串、备份、还原、SQL监视相关设置

目录 一、连接字符串 【本地连接字符串】 【远程连接字符串】 二、备份 三、还原 &#xff08;1&#xff09;还原数据库-bak、btn文件 &#xff08;2&#xff09;附加数据库mdf文件 四、SQL监视器的使用 一、连接字符串 【本地连接字符串】 server DESKTOP-FTH2P3S; Da…

大数据-212 数据挖掘 机器学习理论 - 无监督学习算法 KMeans 基本原理 簇内误差平方和

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

Go语言的并发安全与互斥锁

线程通讯 在程序中不可避免的出现并发或者并行&#xff0c;一般来说对于一个程序大多数是遵循开发语言的启动顺序。例如&#xff0c;对于go语言来说&#xff0c;一般入口为main&#xff0c;main中依次导入import导入的包&#xff0c;并按顺序执行init方法&#xff0c;之后在按…