【算法详解】二分查找

1. 二分查找算法介绍

「二分查找算法(Binary Search Algorithm)」:也叫做 「折半查找算法」「对数查找算法」。是一种在有序数组中查找某一特定元素的搜索算法。

基本算法思想:先确定待查找元素所在的区间范围,在逐步缩小范围,直到找到元素或找不到该元素为止。

二分查找算法的过程如下所示:

  1. 每次查找时从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;
  2. 如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。
  3. 如果在某一步骤数组为空,则代表找不到。

举个例子来说,给定一个有序数组 [0, 1, 2, 3, 4, 5, 6, 7, 8]。如果我们希望查找 5 是否在这个数组中。

  1. 第一次区间为整个数组 [0, 1, 2, 3, 4, 5, 6, 7, 8],中位数是 4,因为 4 小于 5,所以如果 5 存在在这个数组中,那么 5 一定在 4 右边的这一半区间中。于是我们的查找范围变成了 [4, 5, 6, 7, 8]
  2. 第二次区间为 [4, 5, 6, 7, 8],中位数是 6,因为 5 小于 6,所以如果 5 存在在这个数组中,那么 5 一定在 6 左边的这一半区间中。于是我们的查找范围变成了 [4, 5, 6]
  3. 第三次区间为 [4, 5, 6],中位数是 5,正好是我们需要查找的数字。

于是我们发现,对于一个长度为 9 的有序数组,我们只进行了 3 次查找就找到了我们需要查找的数字。而如果是按顺序依次遍历数组,则最坏情况下,我们需要查找 9 次。

二分查找过程的示意图如下所示:

2. 二分查找算法思想

二分查找算法是经典的 「减而治之」 的思想。

这里的 「减」 是减少问题规模的意思,「治」 是解决问题的意思。「减」「治」 结合起来的意思就是 「排除法解决问题」。即:每一次查找,排除掉一定不存在目标元素的区间,在剩下可能存在目标元素的区间中继续查找。

每一次通过一些条件判断,将待搜索的区间逐渐缩小,以达到「减少问题规模」的目的。而于问题的规模是有限的,经过有限次的查找,最终会查找到目标元素或者查找失败。

3. 查找的三种常见模板

3.1模板I

模板I是二分查找的基础模板,适用于可以通过访问数组中单个索引来确定元素或条件的情况。关键属性包括:

  1. 查找条件可以在不与元素的两侧进行比较的情况下确定。
  2. 不需要后处理,每一步都在检查是否找到了元素。
int binarySearch(vector<int>& nums, int target) {if (nums.size() == 0) return -1;int left = 0, right = nums.size() - 1;while (left <= right) {int mid = left + (right - left) / 2;if (nums[mid] == target) return mid;else if (nums[mid] < target) left = mid + 1;else right = mid - 1;}return -1;
}

3.2模板II

模板II是二分查找的高级模板,适用于需要访问数组中当前索引及其直接右邻居索引的情况。关键属性包括:

  1. 查找条件需要访问元素的直接右邻居。
  2. 使用元素的右邻居来确定是否满足条件,并决定是向左还是向右。
  3. 保证查找空间在每一步中至少有2个元素。
  4. 需要进行后处理,当剩下1个元素时,需要评估剩余元素是否符合条件。
int binarySearch(vector<int>& nums, int target) {if (nums.size() == 0) return -1;int left = 0, right = nums.size();while (left < right) {int mid = left + (right - left) / 2;if (nums[mid] == target) return mid;else if (nums[mid] < target) left = mid + 1;else right = mid;}if (left != nums.size() && nums[left] == target) return left;return -1;
}

3.3 模板III

模板III是二分查找的另一种形式,适用于搜索需要访问当前索引及其在数组中的直接左右邻居索引的情况。关键属性包括:

  1. 搜索条件需要访问元素的直接左右邻居。
  2. 使用元素的邻居来确定是向右还是向左。
  3. 保证查找空间在每个步骤中至少有3个元素。
  4. 需要进行后处理,当剩下2个元素时,需要评估其余元素是否符合条件。
int binarySearch(vector<int>& nums, int target) {if (nums.size() == 0) return -1;int left = 0, right = nums.size() - 1;while (left + 1 < right) {int mid = left + (right - left) / 2;if (nums[mid] == target) return mid;else if (nums[mid] < target) left = mid;else right = mid;}if (nums[left] == target) return left;if (nums[right] == target) return right;return -1;
}

4 题目描述

给定一个按照升序排列的长度为n的整数数组,以及 q 个查询。对于每个查询,返回一个元素k的起始位置和终止位置(位置从0开始计数)。如果数组中不存在该元素,则返回“-1 -1”。

输入格式

  • 第一行包含整数n和q,表示数组长度和询问个数。
  • 第二行包含n个整数(均在1~10000范围内),表示完整数组。
  • 接下来q行,每行包含一个整数k,表示一个询问元素。

输出格式

  • 共q行,每行包含两个整数,表示所求元素的起始位置和终止位置。如果数组中不存在该元素,则返回“-1 -1”。

数据范围

  • 1 ≤ n ≤ 100000
  • 1 ≤ q ≤ 10000
  • 1 ≤ k ≤ 10000

输入样例

6 3
1 2 2 3 3 4
3
4
5

输出样例

3 4
5 5
-1 -1

题解思路

这道题可以使用二分查找来解决。我们首先实现两个二分查找函数,一个用于找到元素k的起始位置,另一个用于找到元素k的终止位置。然后,对于每个查询,我们使用这两个函数分别找到起始位置和终止位置,并输出结果。

参考代码

#include<iostream>
using namespace std;int n, q;
const int N = 100010;
int a[N];int binary_search(int k) {int l = 0, r = n - 1;while (l < r) {int mid = l + r >> 1;if (a[mid] < k) l = mid + 1;else r = mid;}return l;
}int binary_search2(int k) {int l = 0, r = n - 1;while (l < r) {int mid = l + r + 1 >> 1;if (a[mid] > k) r = mid - 1;else l = mid;}return l;
}int main() {scanf("%d%d", &n, &q);for (int i = 0; i < n; i++)scanf("%d", &a[i]);while (q--) {int temp;scanf("%d", &temp);int p = binary_search(temp);int q = binary_search2(temp);if (a[p] == temp)cout << p << " " << q << endl;else cout << "-1 -1" << endl;}return 0;
}

这样,我们就完成了对这道题目的解答。通过这个例子,我们可以看到二分查找在处理有序数组时的应用,以及如何利用二分查找来解决一些问题。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ROS通信机制---服务通信

2.2服务通信 2.2.1服务通信理论模型 2.2.2服务通信自定义srv 后续调用相关msg时&#xff0c;是从这些中间文件调用的 2.2.3服务通信自定义srv调用A&#xff08;c&#xff09; 1.服务端 2.客户端 实现参数的动态提交 问题&#xff1a;如果先启动客户端&#xff0c;那么会请求异…

避雷!网络安全学习五大误区,你还不知道?

尽管安全问题老生常谈&#xff0c;但一些普遍存在的误区仍然可能让企业随时陷入危险境地。 为了有效应对当前层出不穷且不断变换的网络威胁&#xff0c;最大程度规避潜在风险&#xff0c;深入了解网络安全的发展趋势必不可少。即使部署了最新且最先进的硬件和解决方案并严格遵守…

技能鉴定试题及答案,分享几个实用搜题和学习工具 #其他#职场发展

市面上搜题软件不少&#xff0c;大部分都挺好用的&#xff0c;今天小编在这里给大家分享几个好用的搜题工具&#xff0c;都拥有丰富的题库资源&#xff1b;而且搜题功能也都很完善&#xff0c;手机端、网页端均有&#xff0c;有需要的小伙伴赶紧码住&#xff01; 1.千鸟搜题 …

配置VM开机自启动

1. 在此电脑-右键选择“管理”-服务和应用程序-服务中找到VMware Workstation Server服务&#xff08;新版名称也可能是VMware自启动服务&#xff0c;自己找一下&#xff0c;服务属性里有描述信息的&#xff09;&#xff0c;将其启用并选择开机自动启动 新版参考官方文档&…

Redis Cluster集群模式

目录 一、理论 1.1 概念 1.2 集群的作用 1.3 redis集群的数据分片 1.4 Redis集群的主从复制模型 二、实践 2.1 Redis集群模式的搭建 2.1.1 cluster集群前期工作 2.1.2 开启群集功能 2.1.3 启动redis节点 2.1.4 启动集群 2.2 测试集群 总结 一、理论 1.1 概念 集群&a…

SpringBoot及其特性

0.前言 Spring 框架提供了很多现成的功能。那么什么是 Spring Boot&#xff1f;使用 Spring 框架&#xff0c;我们可以避免编写基础框架并快速开发应用程序。为了让 Spring 框架提供基础框架&#xff0c;我们需要向 Spring 框架描述有关我们的应用程序及其组件的信息。 不只是…

混合云构建-如何通过Site to Site VPN 连接 AWS 和GCP云并建立一个高可用的VPN通信

如果我们的业务环境既有AWS云又有GCP云,那么就需要将他们打通,最经济便捷的方式就是通过Site-to-Site VPN连接AWS和GCP云,你需要在两个云平台上分别配置VPN网关,并建立一个VPN隧道来安全地连接这两个环境,稍微有些复杂繁琐,以下是详细步骤的动手实践: 一、在GCP 云中创…

深入了解iOS内存(WWDC 2018)笔记-内存诊断

主要记录下用于分析iOS/macOS 内存问题的笔记。 主要分析命令&#xff1a; vmmap, leaks, malloc_history 一&#xff1a;前言 有 3 种思考方式 你想看到对象的创建吗&#xff1f;你想要查看内存中引用对象或地址的内容吗&#xff1f;或者你只是想看看 一个实例有多大&#…

LiveGBS流媒体平台GB/T28181常见问题-系统服务日志如何配置日志个数日志路径日志时长web操作日志操如何配置保留天数及过滤

LiveGBS系统服务日志如何配置日志个数日志路径日志时长web操作日志操如何配置保留天数及过滤 1、系统服务日志1.1、日志目录1.2、配置日志文件个数及记录时间1.3、配置日志文件路径 2、Web 操作日志2.1、配置保留天数2.2、配置不记录操作日志2.1.1、不记录所有2.1.2、不记录指定…

JS与Python函数在语法的区别

区别 标题语法&#xff1a;Python使用缩进来表示代码块&#xff0c;而JavaScript使用大括号{}。 Python函数定义&#xff1a; def my_function():# 函数体JavaScript函数定义&#xff1a; function myFunction() {// 函数体 }标题参数传递&#xff1a;Python支持位置参数、…

智能化办公时代来临:AI助你解放双手

文章目录 一、AI在办公领域的广泛应用二、AI助力办公效率提升1.自动化流程减少繁琐任务2.智能分析辅助决策制定3.个性化服务提升用户体验 三、AI提升办公效率的未来趋势1.更加智能化的办公场景2.更高效的团队协作3.更全面的数据安全保护 四、应对AI带来的挑战《AI高效工作一本通…

【二分查找】Leetcode 山脉数组的峰顶索引

题目解析 852. 山脉数组的峰顶索引 这到题使用暴力枚举的查找方法发现这段数组是有二段性的&#xff0c;峰顶左边的一段区间是一段递增区间&#xff0c;右边的一段区间是一段递减区间 算法讲解 class Solution { public:int peakIndexInMountainArray(vector<int>&am…