[STL] 标准二分算法模板 lower_bound() upper_bound()代码解析

2024-02-07 03:38

本文主要是介绍[STL] 标准二分算法模板 lower_bound() upper_bound()代码解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、摘要

二分算法是经常使用的算法之一,熟练使用二分算法是一个程序员的基本素养。C++的<algorithm>头文件中存在lower_bound()upper_bound()函数,支持在已排好序的容器中查找首个大于等于或者大于目标元素的迭代器位置。同时在有序容器类,例如set<>和map<>,也存在类似功能的函数。熟练使用lower_bound()upper_bound()函数可以方便地使用二分算法解决问题。本文基于< algorithm>源代码,对lower_bound()upper_bound()代码进行分析解释,对于实现严谨高效的二分算法具有重要参考价值。

本文在第二部分给出<algorithm>头文件中lower_bound()upper_bound()函数的代码,并对代码进行分析解释;第三部分是对二分算法的实现,并注明了实现中需要注意的事项;最后一部分是本文参考文章链接。

二、官方代码

1. lower_bound(first, last, value)

lower_bound(first, last, value)函数根据给定的value值,返回[first, last)范围中第一个大于等于value的迭代器(元素位置)。若无法找到,则返回last,因此实际可供返回的范围为[first,last]。

源代码
template<class ForwardIt, class T>
ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value)
{ForwardIt it;// 用于表示[first, last)的中间位置值// count 表示待搜索的容器中元素个数,初始为last-first// step 用来得到待搜索范围的中间位置typename std::iterator_traits<ForwardIt>::difference_type count, step;// distance即计算[first,last)中间的元素个数count = std::distance(first, last);// 若待搜索的容器中元素个数大于0个,则进入while循环while (count > 0) {it = first; // (1) step = count / 2;// (2) 求中间位置元素距离first的间隔std::advance(it, step);	// (3),这三行用来使it等于[first,last)的中间位置。// 例如:若待搜索的容器为{0,1,2,3},那么first指向元素0的位置,last指向3后面的一个位置,count为容器中元素的个数等于4,it指向0+4/2=2,即it指向2;// 若待搜索的容器为{0,1,2},那么first指向元素0的位置,last指向3后面的一个位置,count为容器中元素的个数等于3,it指向0+3/2=1,即it指向1;// 即若容器中元素个数为奇数,it指向中间位置的元素;若容器中个数为偶数,则it指向中间两个元素中后一个元素。if (*it < value) {// 若中间元素it小于value,则说明最终需要返回的元素在[it+1,last)范围内first = ++it; //则将first赋值为it+1位置处count -= step + 1; // 更新现在的搜索范围内的元素个数。count变为count - [first,it]范围内的元素个数,即count -= (step+1)}else// 若中间元素it大于等于value,则说明最终需要返回的元素在[first,it]// 因此此时不需要更改first位置,只需要令搜索范围变为[first,it),即count变为step即可;// 此处新的搜索范围变为[first,it)而不是[first,it]的原因是,若[first,it)范围内找不到大于等于value的元素,则返回[first,it)范围内最后一个元素(it-1)的下一个元素位置(it)正好可以得到[first,last)范围内第一个大于等于value的位置。// 同时,这样设置保证了每次循环的count值都变小。若初始容器为{10,10},value = 5,那么若此处更新使用count=step+1,则会形成死循环。count = step;}// 返回结果第一个大于等于value的元素位置,若没有则first会指向last,即返回last。return first;
}

可以将lower_bound()函数理解为一个递归函数,该函数用于求在范围[first, first+count)范围内第一个大于等于value的元素,若不存在返回first+cound,只不过是使用while循环实现。在具体实现中保证了count每次循环都变为原来的一半,因此算法复杂度为log(n)。

2. upper_bound(first, last, value)

upper_bound()函数与lower_bound()函数类似,只不过将判断条件if (*it < value)变为if (!(value < *it)),其他部分都相同,因此对upper_bound()函数不在添加注释。

template<class ForwardIt, class T>
ForwardIt upper_bound(ForwardIt first, ForwardIt last, const T& value)
{ForwardIt it;typename std::iterator_traits<ForwardIt>::difference_type count, step;count = std::distance(first, last);while (count > 0) {it = first; step = count / 2; std::advance(it, step);if (!(value < *it)) {first = ++it;count -= step + 1;} elsecount = step;}return first;
}

三、二分算法实现

1. 二分算法实现

基于第二部分中给出的代码,我们参考其代码结构给出二分算法的实现。
算法需要解决的问题为,给出一个递增数组nums,求数组中第一个大于等于value的值的下标,若不存在则输出“不存在”。
实现代码如下:

#include<iostream>
#include<vector>
using namespace std;
int main(){vector<int> nums = {0,1,2,3,4,5,6,7,8,9};int value = 5;int first = 0;int last = nums.size();int step;int count = last-first;int middle;while(count>0){step = count/2;middle = first+step;if(nums[middle]<value){first = middle+1;count = count - (step+1);}else{count = step;}}if(first<nums.size()){cout<<"第一个大于等于"<<value<<"的元素下标为:"<<first<<endl;}else{cout<<"数组nums中没有大于等于"<<value<<"的元素"<<endl;}return 0;
}

程序输出为:

第一个大于等于5的元素下标为:5

2. 注意事项

  • 使用二分算法要求数组有序(递增)。若数组递减,可以使用lower_bound(first, last, cmp)函数自定义比较函数。
  • 在while()循环中每次都要使count变为上次循环的一半大小(或者一半减一),不然容易造成死循环。

四、参考

[1]. std::lower_bound
[2]. std::upper_bound

这篇关于[STL] 标准二分算法模板 lower_bound() upper_bound()代码解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/686422

相关文章

线上Java OOM问题定位与解决方案超详细解析

《线上JavaOOM问题定位与解决方案超详细解析》OOM是JVM抛出的错误,表示内存分配失败,:本文主要介绍线上JavaOOM问题定位与解决方案的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一、OOM问题核心认知1.1 OOM定义与技术定位1.2 OOM常见类型及技术特征二、OOM问题定位工具

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

Python实现Excel批量样式修改器(附完整代码)

《Python实现Excel批量样式修改器(附完整代码)》这篇文章主要为大家详细介绍了如何使用Python实现一个Excel批量样式修改器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录前言功能特性核心功能界面特性系统要求安装说明使用指南基本操作流程高级功能技术实现核心技术栈关键函

深度解析Python中递归下降解析器的原理与实现

《深度解析Python中递归下降解析器的原理与实现》在编译器设计、配置文件处理和数据转换领域,递归下降解析器是最常用且最直观的解析技术,本文将详细介绍递归下降解析器的原理与实现,感兴趣的小伙伴可以跟随... 目录引言:解析器的核心价值一、递归下降解析器基础1.1 核心概念解析1.2 基本架构二、简单算术表达

深度解析Java @Serial 注解及常见错误案例

《深度解析Java@Serial注解及常见错误案例》Java14引入@Serial注解,用于编译时校验序列化成员,替代传统方式解决运行时错误,适用于Serializable类的方法/字段,需注意签... 目录Java @Serial 注解深度解析1. 注解本质2. 核心作用(1) 主要用途(2) 适用位置3

Java MCP 的鉴权深度解析

《JavaMCP的鉴权深度解析》文章介绍JavaMCP鉴权的实现方式,指出客户端可通过queryString、header或env传递鉴权信息,服务器端支持工具单独鉴权、过滤器集中鉴权及启动时鉴权... 目录一、MCP Client 侧(负责传递,比较简单)(1)常见的 mcpServers json 配置

Redis实现高效内存管理的示例代码

《Redis实现高效内存管理的示例代码》Redis内存管理是其核心功能之一,为了高效地利用内存,Redis采用了多种技术和策略,如优化的数据结构、内存分配策略、内存回收、数据压缩等,下面就来详细的介绍... 目录1. 内存分配策略jemalloc 的使用2. 数据压缩和编码ziplist示例代码3. 优化的

从原理到实战解析Java Stream 的并行流性能优化

《从原理到实战解析JavaStream的并行流性能优化》本文给大家介绍JavaStream的并行流性能优化:从原理到实战的全攻略,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的... 目录一、并行流的核心原理与适用场景二、性能优化的核心策略1. 合理设置并行度:打破默认阈值2. 避免装箱

Maven中生命周期深度解析与实战指南

《Maven中生命周期深度解析与实战指南》这篇文章主要为大家详细介绍了Maven生命周期实战指南,包含核心概念、阶段详解、SpringBoot特化场景及企业级实践建议,希望对大家有一定的帮助... 目录一、Maven 生命周期哲学二、default生命周期核心阶段详解(高频使用)三、clean生命周期核心阶