如何在C++的STL中巧妙运用std::find实现高效查找

如何在C++的STL中巧妙运用std::find实现高效查找

  • 一、简介
  • 二、在那里吗?
    • 2.1、在未排序的元素上
    • 2.2、已排序元素
  • 三、在哪里?
    • 3.1、在未排序的元素上
    • 3.2、已排序元素
  • 四、应该在哪里?
  • 五、结论

一、简介

本文章旨在涵盖所有在STL中(甚至稍微超出)关于搜索的知识,尽管在集合中搜索某些东西的需求是一个很容易理解的概念,但是要彻底地涵盖这个主题,还有很多内容要讲。

本文主要介绍在元素范围上执行搜索的经典STL算法,后面会继续讲解当直接访问STL容器时,如何执行有效和正确的搜索,而不是简单的范围;以及介绍的绝大多数开发人员不知道但很有用的算法

这篇文章展示了如何在一个范围内搜索。这里将坚持使用标准版本的STL,并考虑由2个迭代器表示的范围。

STL可以被分成两部分:对已排序元素进行操作的部分以及对未排序元素进行操作的部分。

这种差异对搜索有两个影响:

  • 在已排序的集合中查找非常快,通常在对数时间内,而在未排序的集合中查找通常在线性时间内。
  • 在已排序范围上显示的所有方法都按照等价性(与<比较)来比较值,而在未排序范围上显示的方法则按照相等性(与==比较)来比较值。

这篇文章将深入探讨以下3个问题:

  • 在那里吗?
  • 在哪里?
  • 应该在哪里(对于排序范围)?

二、在那里吗?

2.1、在未排序的元素上

这个问题可以用std::find来表示,并结合与范围末尾的比较:

vector<int> v = {...}; // v filled with values
if (std::find(v.begin(), v.end(), 42) != v.end())
{
    ...
}

当然,也可以用std::count来表示。

vector<int> v = {...}; // v filled with values
if (std::count(v.begin(), v.end(), 42))
{
    ...
}

返回值在if语句中隐式地转换为bool值:在这里,如果范围内至少有一个元素等于42,则计算结果为true

std::find相比,std::count方法有优点也有缺点。

std::count的优点:std::count避免与结束操作符进行比较。

std::count的缺点:

  • std::count遍历整个集合,而std::find在搜索到第一个与搜索值相等的元素时就返回
  • std::find更好地表达了正在查找的内容。

因此,std::find更常用。

注意,要检查是否存在满足谓词而不等于值的元素,请使用std::count_ifstd::find_ifstd::find_if_not,这应该是必知的。

2.2、已排序元素

关于已排序元素,要使用的算法是std::binary_search,它直接返回一个bool值,表示搜索值是否在集合中具有等效元素。

std::set<int> numbers = {...}; // sorted elements
bool is42InThere = std::binary_search(numbers.begin(), numbers.end(), 42);

三、在哪里?

更准确地说,希望获得指向搜索元素出现位置的迭代器。

3.1、在未排序的元素上

使用std::find。它将返回指向第一个与搜索值相等的元素的迭代器,如果没有找到该值,则返回指向集合末尾的迭代器。

std::vector<int> numbers = {...};
auto searchResult = std::find(numbers.begin(), numbers.end(), 42);

if (searchResult != numbers.end()) {
    ...
}

在这里插入图片描述

3.2、已排序元素

对于已排序的集合,STL没有像std::find那样简单的算法。但是std::find并不是真正为排序集合而设计的,因为它使用的是相等而不是等价,并且它在线性时间而不是对数时间内操作。

对于给定的集合,如果确定现在和将来元素的类型的相等性与等价性是相同的,并且接受付出线性时间,std::find将是不错的选择,引起它是简单的接口。必须注意,std::find不是专门为排序范围进行操作而设计的。

使用的算法是std::equal_range,函数原型:

template< class ForwardIt, class T >
std::pair<ForwardIt,ForwardIt> equal_range( ForwardIt first, ForwardIt last, const T& value );

std::equal_range 返回与搜索值等价的元素范围。该范围由指向集合内部的 std::pair 迭代器对表示, 该对中的两个迭代器分别表示与搜索值等价的子范围中第一个和最后一个元素。

它的接口使用起来有些笨拙:

std::vector<int> v = {3, 7, 3, 11, 3, 3, 2};
sort(v.begin(), v.end());

// equal_range, attempt 1: natively clumsy
std::pair<std::vector<int>::iterator, std::vector<int>::iterator> range1 = equal_range(v.begin(), v.end(), 3);
std::for_each(range1.first, range1.second, doSomething);

使用类型定义可以使其更轻巧:

std::vector<int> v = {3, 7, 3, 11, 3, 3, 2};
sort(v.begin(), v.end());

using IteratorPair = std::pair<std::vector<int>::iterator, std::vector<int>::iterator>;

// equal_range, attempt 2: with the classical typedef
IteratorPair range2 = equal_range(v.begin(), v.end(), 3);
std::for_each(range2.first, range2.second, doSomething);

上面的示例确实少了笨拙,但仍然有一个基本的问题:抽象的层次没有得到遵守。事实上,当操作由equal_range返回的东西时,pair强迫使用代码的“first”和“second”,而它应该是一个范围。而一个范围应该用“begin”和“end”来表达。

为了解决这个问题,可以使用一个类将std::equal_range返回的一对迭代器包装成一个具有范围语义的对象:

template<typename Container>
class Range
{
public:
    Range(std::pair<typename Container::iterator, typename Container::iterator> range)
    : m_begin(range.first), m_end(range.second)
    {}
    typename Container::iterator begin() { return m_begin; }
    typename Container::iterator end() { return m_end; }
 
private:
    typename Container::iterator m_begin;
    typename Container::iterator m_end;
};

这类存在于诸如Boost.range或range-v3之类的range库中。如果查看它们的实现代码(这里是boost的实现代码,这里是range-v3的实现代码),会发现它们包含的内容远不止上面的简单包装器,这里只是为了说明要点,而不是用于实际生产代码)。

这有效地将一对迭代器提升到范围的抽象级别。

注意,如果没有包装器,std::beginstd::end不能用于std::equal_range的结果,即使它是一个范围!包装器才能真正解决了这个问题。

使用方式:

std::vector<int> v = {3, 7, 3, 11, 3, 3, 2};
sort(v.begin(), v.end());
 
// equal_range, attempt 3: natural al last
Range<std::vector<int>> range3 = equal_range(v.begin(), v.end(), 3);
std::for_each(range3.begin(), range3.end(), doSomething);

无论使用上述哪种方法,equal_range都会返回一个范围,所以可以通过比较两个迭代器来检查它是否为空,并使用std::distance来检查它的大小:

bool noElementFound = range3.begin() == range3.end();
size_t numberOfElementFound = std::distance(range3.begin(), range3.end())

四、应该在哪里?

这个问题只对已排序的范围有意义,因为对于未排序的范围,元素可以在范围中的任何位置。对于一个已排序的范围,更精确地说是:“如果它在那里,那么它在哪里,如果它不在那里,那么它应该在哪里?”。有点绕,对不对,没关系,继续往下阅读。

这个问题可以用std::lower_boundstd::upper_bound两种算法表示。

一旦理解了std::equal_range,就很容易理解它们了:std::lower_boundstd::upper_bound分别返回std::equal_range返回的第一个迭代器和第二个迭代器。

因此,

  • 要在范围内插入一个值,使其位于与该值相等的元素之前,使用std::lower_bound获取一个迭代器,指定要插入的位置。
  • 要在范围内插入一个值,使其位于与该值相等的元素之后,使用std::upper_bound获取一个迭代器,指定要插入的位置。

注意,一般不要使用std::lower_boud来搜索元素:不像std::find,不能简单地检查std::lower_bound返回的迭代器是否与end不同,从而知道元素是否在集合中。如果元素不存在,std::lower_bound返回它应该在的位置,而不是集合的末尾。因此,需要检查返回的迭代器是否不在范围的末尾,并检查它指向的元素的值是否与搜索的元素的值相等。

要特别要小心:相等,不相等。如果对类型并不意味着相同的事情,则需要编写等价测试,通常采用!(a < b) && !(b < a)的形式。如果排序比较器不是operator<,而是自定义比较器,则需要使用自定义比较器。如果比较器发生变化,一定要更新代码。建议,只需使用std::equal_range即可

五、结论

下面的表格总结了在一个范围内搜索时使用的算法:

C++表达不排序排序
在那里吗?std::find != endstd::binary_search
在哪里?std::findstd::equal_range
应该在哪里?std::lower_bound、std::upper_bound

在下一篇文章中,将了解如何直接在标准容器中搜索,而不是在范围中搜索。
在这里插入图片描述

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

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

相关文章

Eclipse 配置JDK版本,Eclipse Maven install 时使用的JDK版本

Eclipse配置JDK版本 Eclipse 配置JDK版本的地方&#xff1f; 在Eclipse中配置JDK版本的步骤如下&#xff1a; 打开Eclipse IDE。转到菜单栏并选择 “Window”&#xff08;窗口&#xff09;选项。在下拉菜单中选择 “Preferences”&#xff08;首选项&#xff09;&#xff0c;或…

asp.net core 依赖注入后的服务生命周期

ASP.NET Core 依赖注入&#xff08;DI&#xff09;容器支持三种服务的生命周期选项&#xff0c;它们定义了服务实例的创建和销毁的时机。理解这三种生命周期对于设计健壯且高效的应用程序非常重要&#xff1a; 瞬时&#xff08;Transient&#xff09;&#xff1a; 瞬时服务每次…

大型网站系统架构演化实例_3.使用服务集群改善网站并发处理能力

1.使用服务集群改善网站并发处理能力 使用集群是网站解决高并发、海量数据问题的常用手段。当一台服务器的处理能力、存储空间不足时&#xff0c;不要企图去更换更强大的服务器&#xff0c;对大型网站而言&#xff0c;不管多么强大的服务器&#xff0c;对大型网站而言&…

算法练习第20天|回溯算法 77.组合问题 257. 二叉树的所有路径

1.什么是回溯算法&#xff1f; 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。其本质是穷举&#xff0c;穷举所有可能&#xff0c;然后选出我们想要的答案。 2.为什么要有回溯算法? 那么既然回溯法并不高效为什么还要用它呢&#xff1f; 因为有的问题能暴力…

第10章 物理安全要求

10.1 站点与设施设计的安全原则 假如没有对物理环境的控制&#xff0c;任何管理的、技术的或逻辑的访问控制技术都无法提供足够的安全性。 如果怀有恶意的人员获取了对设施及设备的物理访问权&#xff0c;那么他们几乎可以为所欲为&#xff0c;包括肆意破坏或窃取、更改数据。…

光伏工程施工前踏勘方案与注意事项

光伏工程是指利用光能发电的技术。随着清洁能源的发展&#xff0c;光伏工程在能源领域的应用越来越广泛。在进行光伏工程施工前&#xff0c;需要对施工现场进行踏勘&#xff0c;以确保施工能够顺利进行并达到预期的效果。 本文游小编带大家一起看一下探勘的方案和注意事项。 1…

设计模式胡咧咧之策略工厂实现导入导出

策略模式&#xff08;Strategy Pattern&#xff09; 定义&#xff1a; 定义了一组算法&#xff0c;将每个算法都封装起来&#xff0c;并且使它们之间可以互换。 本质: 分离算法&#xff0c;选择实现 应用场景 何时使用 一个系统有许多类&#xff0c;而区分他们的只是他们直接…

【赛题】2024年“华中杯”数模竞赛赛题发布

2024年"华中杯"数学建模网络挑战赛——正式开赛&#xff01;&#xff01;&#xff01; 赛题已发布&#xff0c;后续无偿分享各题的解题思路、参考文献&#xff0c;帮助大家最快时间&#xff0c;选择最适合是自己的赛题。祝大家都能取得一个好成绩&#xff0c;加油&a…

Python数据可视化:散点图matplotlib.pyplot.scatter()

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 Python数据可视化&#xff1a; 散点图 matplotlib.pyplot.scatter() 请问关于以下代码表述错误的选项是&#xff1f; import matplotlib.pyplot as plt x [1, 2, 3, 4, 5] y [2, 3, 5, 7,…

认识一下RAG

1.RAG技术背景与挑战 2.RAG的核心概念 3.RAG的工作流程与架构 4.RAG的优化方法 RAG的提出 •Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks是一篇重要的论文(2020年5月) •REALM: Retrieval-Augmented Language Model Pre-Training (2020)就将BERT预训练模…

libVLC Ubuntu编译详解

1.简介 有时候&#xff0c;windows上开发不满足项目需求&#xff0c;需要移植到linux上&#xff0c;不得不自行编译libvlc&#xff0c;编译libvlc相对而言稍微麻烦一点。 我使用的操作系统&#xff1a;Ubuntu20.04 查看系统命令lsb_release -a libvlc版本&#xff1a; 3.0.1…

CSS导读 (CSS的三大特性 上)

&#xff08;大家好&#xff0c;今天我们将继续来学习CSS的相关知识&#xff0c;大家可以在评论区进行互动答疑哦~加油&#xff01;&#x1f495;&#xff09; 目录 五、CSS的三大特性 5.1 层叠性 5.2 继承性 5.2.1 行高的继承 5.3 优先级 小练习 五、CSS的三大特性 …

Goland远程连接Linux进行项目开发

文章目录 1、Linux上安装go的环境&#xff12;、配置远程连接3、其他配置入口 跑新项目&#xff0c;有个confluent-Kafka-go的依赖在Windows上编译不通过&#xff0c;报错信息&#xff1a; undefined reference to __imp__xxx似乎是这个依赖在Windows上不支持&#xff0c;选择让…

数据库设计的三范式

简单来说就是&#xff1a;原子性、唯一性、独立性 后一范式都是在前一范式已经满足的情况进行附加的内容 第一范式&#xff08;1NF&#xff09;&#xff1a;原子性 存储的数据应不可再分。 不满足原子性&#xff1a; 满足原子性&#xff1a; 第二范式&#xff08;2NF&#xf…

历史遗留问题1-Oracle Mysql如何存储数据、索引

在学习到Oracle redo和undo时&#xff0c;涉及到很多存储结构的知识&#xff0c;但是网上的教程都不是很详细&#xff0c;就去复习了一下mysql&#xff0c;感觉是不是开源的问题&#xff0c;Mysql的社区和知识沉淀远高于Oracle&#xff0c; 对于初学者很友好&#xff0c;我想请…

生成人工智能体:人类行为的交互式模拟论文与源码架构解析(5)——可控评估端到端评估

最后完结篇,文末有测试中发现的有趣现象,并附上了相关资料链接~ 5.可控评估 分两个阶段评估生成代理。我们从一个更加严格控制的评估开始,单独评估代理的响应,以了解它们是否在狭义上定义的上下文中产生可信的行为。然后,在我们对代理社区进行为期两天的端到端分析中,我…

初始C++

1. C关键字(C98) C总计63个关键字&#xff0c; C语言32个关键字 ps&#xff1a;下面我们只是看一下C有多少关键字&#xff0c;不对关键字进行具体的讲解。后面我们学到以后再 细讲。 2. 命名空间 在C/C中&#xff0c;变量、函数和后面要学到的类都是大量存在的&#xff0c;…

llama-factory SFT系列教程 (三),chatglm3-6B 大模型命名实体识别实战

文章列表&#xff1a; llama-factory SFT系列教程 (一)&#xff0c;大模型 API 部署与使用llama-factory SFT系列教程 (二)&#xff0c;大模型在自定义数据集 lora 训练与部署 llama-factory SFT系列教程 (三)&#xff0c;chatglm3-6B 命名实体识别实战 简介 利用 llama-fa…

opencv | 编译缺失ippicv相关文件解决方案

1.执行cmake后&#xff0c;查看控制台输出信息 ~/VM_data/opencv-4.9.0$ cd buile_temp ~/VM_data/opencv-4.9.0/buile_temp$ cmake ..2.去浏览器打开链接&#xff0c;下载对应的压缩包&#xff0c;解压到 路径&#xff1a;/3rdparty/ippicv/

Ubuntu 安装 wine

本文所使用的 Ubuntu 系统版本是 Ubuntu 22.04 ! 如果你使用 Ubuntu 系统&#xff0c;而有些软件只在 Windows 上运行&#xff0c;例如&#xff1a;PotPlayer&#xff0c;那么该如何在 Ubuntu 系统中使用到这些 Windows 的软件呢&#xff1f;答案是安装 wine。 简单的安装步骤如…