SGISTL源码探究-stl_alog.h中的二分查找算法

国央企创新负责人如何通过平台实现科研成果的快速识别与转化?.docx 科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。 立即下载

前言

在上一小节中我们分析了stl_algo.h中的部分算法。本小节中我们将继续分析其中关于二分查找类的算法,即lower_bound、upper_bound、binary_search、equal_range。这些算法相比于上一节的要稍微复杂些,如果你对二分法比较了解,这一小节应该也很轻松。

STL中的二分查找算法

lower_bound

使用该函数的前提是传入的区间必须有序,这也是二分法的唯一要求。lower_bound的作用是在[first, last)中寻找第一个大于或者等于value值的元素,然后返回指向该元素的迭代器;也可以理解为在不破坏排序的前提下,寻找value可以插入的点(如果没有则返回last),比如{1, 3, 5, 5, 7, 9},value = 4,那么返回的迭代器应该指向第一个值为5的元素。

在它的源码实现中,同样有两个不同的版本,其中一个版本是使用<操作符进行比较,另一种是传入仿函数comp进行比较,这里我们只分析版本一,另外一个版本与其十分类似。

首先从大体上来看,lower_bound调用了__lower_bound,而__lower_bound有两个不同的版本,其中一个针对迭代器类型为ForwardIterator,另一个针对RandomAccessIterator类型的。这样的区分已经很常见了。

下面让我们来看看它的源码是如何实现的:

//迭代器类型为ForwardIterator版
template <class ForwardIterator, class T, class Distance>
ForwardIterator __lower_bound(ForwardIterator first, ForwardIterator last,
                              const T& value, Distance*,
                              forward_iterator_tag) {
  //len表示区间的长度
  Distance len = 0;
  distance(first, last, len);
  //half表示中间位置
  Distance half;
  ForwardIterator middle;

  while (len > 0) {
    half = len >> 1;
    //让middle指向中间位置
    middle = first;
    advance(middle, half);
    /* 若中间位置的元素的值小于value
     * 证明大于或者等于value的元素在另外一半区间
     * 将first指向中间位置的下一个元素
     * 更新len,重进循环判断
     * 否则证明大于或等于value的元素就在本区间内
     * 更新len,重进循环判断
     */
    if (*middle < value) {
      first = middle;
      ++first;
      len = len - half - 1;
    }
    else
      len = half;
  }
  return first;
}

//RandomAccessIterator版
template <class RandomAccessIterator, class T, class Distance>
RandomAccessIterator __lower_bound(RandomAccessIterator first,
                                   RandomAccessIterator last, const T& value,
                                   Distance*, random_access_iterator_tag) {
  //计算len的长度,RandomAccessIterator才支持last - first这种写法
  Distance len = last - first;
  Distance half;
  RandomAccessIterator middle;

  while (len > 0) {
    //计算half
    half = len >> 1;
    //将middle指向中间位置,RandomAccessIterator才支持first + half这种写法
    middle = first + half;
    if (*middle < value) {
      //同样的,RandomAccessIterator才支持这样
      //不然就只能写成first = middle;++first;这样了
      first = middle + 1;
      len = len - half - 1;
    }
    else
      len = half;
  }
  return first;
}
//调用__lower_bound,使用distance_type以及iterator_category获取到迭代器的相关类型
template <class ForwardIterator, class T>
inline ForwardIterator lower_bound(ForwardIterator first, ForwardIterator last,
                                   const T& value) {
  return __lower_bound(first, last, value, distance_type(first),
                       iterator_category(first));
}
upper_bound

该函数也同样要求区间有序,它的作用是在不破坏有序的前提下,寻找可插入value的最后一个合适的位置。

即假如区间中有value的值,它会返回指向所有值为value的最后一个元素的下一个位置;假如区间中没有value,这个和lower_bound一样,返回指向第一个大于value的元素的位置。

它的实现同样也有两个版本,一个是根据<操作符进行比较,另一个是根据传入的comp进行比较。在lower_bound中,我们是分析的版本一,为了对称比较,这里我们也只分析版本一。

下面是它的源码实现:

//ForwardIterator版
template <class ForwardIterator, class T, class Distance>
ForwardIterator __upper_bound(ForwardIterator first, ForwardIterator last,
                              const T& value, Distance*,
                              forward_iterator_tag) {
  //这部分是完全相同的
  Distance len = 0;
  distance(first, last, len);
  Distance half;
  ForwardIterator middle;

  while (len > 0) {
    half = len >> 1;
    //将middle指向中间位置
    middle = first;
    advance(middle, half);
    /* 如果中间位置的元素值大于value
     * 证明要找的元素就在本区间内了,更新len
     * 否则证明要找的元素在另外一个区间
     * 将first指向middle的下一个元素,更新len
     */
    if (value < *middle)
      len = half;
    else {
      first = middle;
      ++first;
      len = len - half - 1;
    }
  }
  return first;
}

//RandomAccessIterator版
template <class RandomAccessIterator, class T, class Distance>
RandomAccessIterator __upper_bound(RandomAccessIterator first,
                                   RandomAccessIterator last, const T& value,
                                   Distance*, random_access_iterator_tag) {
  //仅RandomAccessIterator支持last - first
  Distance len = last - first;
  Distance half;
  RandomAccessIterator middle;
  //以下实现逻辑与ForwardIterator版相同
  //只是操作更简单,效率更高
  while (len > 0) {
    half = len >> 1;
    middle = first + half;
    if (value < *middle)
      len = half;
    else {
      first = middle + 1;
      len = len - half - 1;
    }
  }
  return first;
}

template <class ForwardIterator, class T>
inline ForwardIterator upper_bound(ForwardIterator first, ForwardIterator last,
                                   const T& value) {
  return __upper_bound(first, last, value, distance_type(first),
                       iterator_category(first));
}
binary_search

该函数的作用即在有序的区间[first, last)中寻找值为value的元素,若有则返回true,否则返回false

它通过内部调用lower_bound实现,并且对应lower_bound,也有两个版本,版本二使用仿函数comp进行比较,而不是通过<操作符。至于为什么不用upper_bound,这是因为就算区间中有等于value的元素,upper_bound返回的迭代器也是最后一个值为value的元素的下一个位置,不满足要求。

源码实现如下:

template <class ForwardIterator, class T>
bool binary_search(ForwardIterator first, ForwardIterator last,
                   const T& value) {
  //调用lower_bound,取得指向等于或者第一个大于value的元素的迭代器
  ForwardIterator i = lower_bound(first, last, value);
  //由于不一定是等于value的元素,所以需要比较判断一下
  //如果等于last,证明没有找到符合条件的元素,或者当*i>value代表也没有值为value的元素,都返回false
  return i != last && !(value < *i);
}

//使用仿函数comp的版本
template <class ForwardIterator, class T, class Compare>
bool binary_search(ForwardIterator first, ForwardIterator last, const T& value,
                   Compare comp) {
  ForwardIterator i = lower_bound(first, last, value, comp);
  return i != last && !comp(value, *i);
}
equal_range

该函数依然要求区间有序,作用是在[first, last)中寻找value,并返回一对迭代器left,right,left指向在不破坏有序的条件下,value可以插入的第一个位置;right指向在不破坏有序的条件下,value可以插入的最后一个位置。

因此,在[first, last)区间内,[left, right)的区间的元素都等于value。它的实现首先也分两个版本,另一个是使用仿函数comp进行比较,我们不作考虑。在版本一的实现中,根据迭代器类型的不同也有不同的实现。

源码如下:

//ForwardIterator版
template <class ForwardIterator, class T, class Distance>
pair<ForwardIterator, ForwardIterator>
__equal_range(ForwardIterator first, ForwardIterator last, const T& value,
              Distance*, forward_iterator_tag) {
  //len表示区间总长度
  Distance len = 0;
  distance(first, last, len);
  Distance half;
  ForwardIterator middle, left, right;

  while (len > 0) {
    half = len >> 1;
    //middle指向中间位置
    middle = first;
    advance(middle, half);
    //若中间值小于value,证明value可能存在于另外一半区间
    if (*middle < value) {
      first = middle;
      ++first;
      len = len - half - 1;
    }
    //若中间值大于value,证明value可能存在本区间内
    else if (value < *middle)
      len = half;
    /* value == *middle
     * 调用lower_bound找到value第一次出现的位置
     * 调用upper_bound找到value最后一次出现的下一个位置
     * 然后构建pair并返回
     */
    else {
      /* middle将区间分成两半,第一半的最大值不会大于middle
       * 由于我们要寻找value第一次出现的位置,所以应该在第一半区间寻找
       * 于是lower_bound寻找的区间是[first, middler)
       * 关于寻找value最后一次出现的下一个位置
       * 很明显该从剩下的那一半区间寻找
       * 所以upper_bound寻找的区间是[++middle, last)
       */
      left = lower_bound(first, middle, value);
      //将first指向last
      advance(first, len);
      right = upper_bound(++middle, first, value);
      return pair<ForwardIterator, ForwardIterator>(left, right);
    }
  }
  //整个区间内没有值为value的元素,则返回一对迭代器,指向第一个大于value的元素(因为first更新时指向大于value的另外一半区间的首个元素)
  return pair<ForwardIterator, ForwardIterator>(first, first);
}

//RandomAccessIterator版,实现逻辑一样,只是修改了表达的方式
template <class RandomAccessIterator, class T, class Distance>
pair<RandomAccessIterator, RandomAccessIterator>
__equal_range(RandomAccessIterator first, RandomAccessIterator last,
              const T& value, Distance*, random_access_iterator_tag) {
  Distance len = last - first;
  Distance half;
  RandomAccessIterator middle, left, right;

  while (len > 0) {
    half = len >> 1;
    //RandomAccessIterator独有的操作
    middle = first + half;
    if (*middle < value) {
      //RandomAccessIterator独有的操作
      first = middle + 1;
      len = len - half - 1;
    }
    else if (value < *middle)
      len = half;
    else {
      left = lower_bound(first, middle, value);
      //RandomAccessIterator独有的操作
      right = upper_bound(++middle, first + len, value);
      return pair<RandomAccessIterator, RandomAccessIterator>(left,
                                                              right);
    }
  }
  return pair<RandomAccessIterator, RandomAccessIterator>(first, first);
}

//根据iterator_category的结果调用合适的__equal_range函数
template <class ForwardIterator, class T>
inline pair<ForwardIterator, ForwardIterator>
equal_range(ForwardIterator first, ForwardIterator last, const T& value) {
  return __equal_range(first, last, value, distance_type(first),
                       iterator_category(first));
}

小结

关于二分查找部分的算法到此为止,在下一小节中我们将分析stl_algo.h中的排列算法部分。

PotPlayer自用皮肤 自用 PotPlayer 皮肤,仅微调了字体大小与颜色,是按个人习惯调整,未做适配性调整。 立即下载

相关推荐

国产系统迁移实战:在银河麒麟V10上搞定Wine32位支持,让老Windows软件跑起来

本文详细介绍了在银河麒麟V10操作系统上构建Wine32位支持的全过程,帮助用户解决Windows应用迁移难题。从系统架构差异分析到Wine环境配置,再到实战应用部署与优化,提供了一套完整的解决方案,确保老Windows软件在国产系统上稳定运行。

weixin_32288959的博客 649

C++基础学习系列--2、递归思想,内置函数,函数重载,函数模板,带默认值函数

alog.h #include using namespace std; // 以下为函数模板 注意!函数模板必须紧跟在template之后定义! template T min(T a, T b) { T min; if (a > b) { min = b; } else min = a; return min; } //递归的思想在于,已知首项和规律,求若干项的答案;

tonepig的博客 273

STM32H723串口接收全为0 & CAN错误中断问题

STM32H723串口接收全为0 和 叠加的导致CAN错误问题

weixin_38743772的博客 3916

STL algorithm源码:stl_algo.h

// Algorithm implementation -*- C++ -*- // Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, // 2010, 2011 // Free Software Foundation, Inc. // // This file is part of the GNU ISO C

roaylchen的博客 6911

忆阻器(二)——忆阻模拟器

除了真实的器件,我们也可以用运放和乘法器来搭忆阻模拟器。这节将介绍数学模型、matlab仿真及multisim仿真。 一、数学模型 忆阻分为磁控和荷控两种,这里介绍三次非线性磁控忆阻模型: {I=W(φ)VW(φ)=a+3bφ2dφdt=V \begin{cases} I=W(\varphi)V \\ W(\varphi)=a+3b \varphi ^2\\ \frac{d \varphi }{dt}=V \end{cases} ⎩⎪⎨⎪⎧​I=W(φ)VW(φ)=a+3bφ2dtdφ​=V​ 这里的WWW

http://www.icfgblog.com/ 1万+

HTML5网页基础设计——课程介绍专栏

案例: 参考代码: <!doctype html> <html> <head> <meta charset="utf-8"> <title>动手实践</title> <style> *{ margin: 0; padding: 0; font-size:14px; } dd{ width: 461px; height: 145px; margin: -4px; pad.

shengming_tutou的博客 4594

【ALGO】基础算法(1)

位运算递推递归等基础算法例题

0x00 1458

[V-10]ARMv8/v9-CPU Virtualization-CPU虚拟化架构简介(vCPU/vPE)

本文简要介绍了CPU虚拟化架构的基础知识。

基层架构师的分享 1267

SGISTL源码探究-STL中的红黑树(上)

前言本小节将进入到SGISTL的红黑树部分。关于红黑树,是一种比较复杂的数据结构,但是并不是特别难。如果对红黑树不太了解,可以去网上查阅相关的资料,因为本文的主要目的是分析STL中的红黑树的源码,和普通的红黑树略有不同,还要复杂一些,需要有一定的基础。基本概念首先什么是二叉搜索树,二叉搜索树即符合任何一个节点的值一定大于其左子树的任何一个节点的值并且小于其右子树的任何一个节点的值(节点的值不允许重复

Move_now的博客 1806

SOEM主站开发篇(1):移植SOEM主站到Linux开发板

详细介绍了SOEM主站源码移植到Linux开发板上的步骤,同时演示了如何使用Linux开发板获取EtherCAT从站的PDO映射信息。

kevin1499的博客 2398

上位机图像处理和嵌入式模块部署(开篇)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 图像处理是现实生活当中很实用的一门技术。工业上一般采用的是机器视觉,以传统算法和光源控制为主,部分采用了深度学习技术。而生活当中,则主要以二维码识别、人脸识别、车牌识别和ocr识别为主,这中间深度学习与AI扮演了很重要的角色。过去,市场上的商业收费软件,主要还是以传统设备厂商提供的上位机软件为主,那这一部分是不是结合最新的npu技术,应该做出一些新的东西出来呢?

嵌入式-老费,一个分享专业嵌入式知识的blog 1334

SGISTL源码探究-STL中的hashtable(上)

前言关于哈希表部分,哈希函数以及解决哈希冲突的方法都有很多种,在本文中不会详细去讨论哈希表的大量知识,而是以SGISTL实现的hashtable为主,所以如果对hashtable不太了解,建议先去网上查阅相关的资料。这里只给出基本的概念。了解哈希表原理基本概念哈希表(hashtable,也称散列表):根据key来访问元素的一种数据结构。它可能先经过哈希函数计算出key,然后通过key访问表中对应ke

Move_now的博客 1483

把审批体验做成一体化:SAP Business Workflow 里的 Secondary Method、Before Method 与 After Method

摘要:SAP Business Workflow 中,Task 的 Secondary、Before 和 After 三类方法对提升审批体验和可追溯性至关重要。Secondary Method 可自动展示辅助信息(如审批时弹出排班报表),但需注意其独立会话特性;Before Method 适合预处理和审计留痕;After Method 确保收尾动作可靠执行。三类方法按特定顺序调用,且错误不影响主流程。合理使用这些方法能减少事务码切换,增强审计能力,优化用户体验。(149字)

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 188

SGISTL源码探究-stl_algobase.h中的算法

前言在上一小节中,我们分析了stl_numeric.h中的算法部分。在本小节中,我们将分析stl_algobase.h文件中的算法,里面实现的都是一些比较基本的算法,比如equal、fill、max、swap等,可以从中学习到泛型的思想。stl_algobase.h中的算法iter_swap该函数的作用是交换迭代器指向的两个元素。注意迭代器的类型必须是ForwardIterator及其派生类。 并

Move_now的博客 1244

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

SGISTL源码探究-STL中的hashtable(下)

前言在上一小节中,我们介绍了hashtable的基本概念,以及分析了在STL中,hashtable的迭代器、采用的数据结构及它的构造/析构函数,还有部分成员函数。 在本小节中,我们讲继续分析剩下的一些操作,如插入、删除、复制等。插入操作关于插入操作,有大量的重载版本,我们只分析比较关键的函数,其他的可以随便看看。/* insert_unique,插入元素(不允许重复) * 首先先判断加上新增元素

Move_now的博客 1184

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

SGISTL源码探究-关联式容器:hash_multimap

前言本小节将介绍hash_multimap的源码实现,它与hash_map的不同与map和multimap的不同类似,所以我们可以一边分析它们的不同之处,一边顺便复习hash_map。hash_multimap的实现定义及数据结构#ifndef __STL_LIMITED_DEFAULT_TEMPLATES template <class Key, class T, class HashFcn =

Move_now的博客 1117

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

SGISTL源码探究-关联式容器:hash_multiset

前言在上几节中我们分析了hashtable以及hash_set还有hash_map的实现,hash_set以及hash_map都不允许有重复的key存在,而hashtable提供了允许插入重复元素的操作insert_equal,对应实现的则是hash_multiset以及hash_multimap。 接下来我们先看看hash_multiset的实现,它和hash_set的实现很类似。hash_mu

Move_now的博客 1090

SGISTL源码探究-stl_algo.h中的排序算法

前言在本小节中,我们将分析STL中算法组件的最后一部分:排序算法。排序算法分为内部排序和外部排序。而内部排序中常见的有八种排序算法:直接插入排序、希尔排序、简单选择排序、堆排序、冒泡排序、快速排序、归并排序、桶排序。在SGISTL实现的sort算法中,并不是简单的使用快速排序作为排序算法,而是交叉使用了插入排序、堆排序以及快速排序,并且做了一定的优化,并且只接受迭代器类型为RandomAccessI

Move_now的博客 938

SGISTL源码探究-stl_algo.h中的排列算法

前言在本小节中,我们将分析stl_algo.h中的排列算法。可能你使用过STL中的next_permutation求过全排列。比如这样:#include <iostream> #include <algorithm> #include <string> using namespace std;int main() { string str; cin >> str; sort(

Move_now的博客 924

SGISTL源码探究-关联式容器:map

前言在本小节中,我们将分析map容器,它与set最大的不同就是,它是key-value型的,而set的key和value是同一个。map和set底层都由红黑树实现,map上存储的都是pair,即键值对,存储到红黑树中时,排序依据的就是pair的第一个元素。 之前提到在set中,不允许更改元素的值,而在map中,key值同样不允许修改,但是value可以修改,因为value不涉及到map整体内部结构

Move_now的博客 913

SGISTL源码探究-关联式容器:hash_set

前言在上小节中,我们已经分析了hashtable的实现,它只是hash_set和hash_map的预备知识。在本小节中,我们将进入到关联式容器hash_set的源码中,一窥究竟。hash_set的实现由于hash_set并没有自己定义的迭代器,所以接下来我们先对它的定义部分以及数据结构进行分析,然后是它的构造函数以及常用操作等。定义部分及数据结构#ifndef __STL_LIMITED_DEFAU

Move_now的博客 876

SGISTL源码探究-大根堆heap

前言小根堆和大根堆的概念在数据结构都讲过,等下简单的过一下就行了。在SGISTL的实现中,它并不作为一种容器,而是一系列的算法,用于给priority_queue提供支持,使优先级队列能够体现其优先级。 在SGISTL实现heap中,采用的数据结构并不是使用二叉树实现,而是采用隐式表达,即使用数组表示一个堆。能用数组表示这是因为堆总是一棵完全二叉树(没有节点漏洞):即若设二叉树的高度为h,除第 h

Move_now的博客 874

SGISTL源码探究-仿函数

前言本小节将介绍STL六大组件之一的仿函数(又称函数对象),在前面分析算法部分时,我们其实已经见识到了仿函数的大量使用(各算法的另一个版本,传入comp仿函数)。关于仿函数的实现,其实就是重载了()运算符,然后调用,所以又称函数对象。接下来我们就来分析该部分。仿函数虽然函数指针也可以完成将各种操作当作参数传入,但是之所以出现了仿函数,是因为函数指针并不能满足STL对抽象的要求,也不能和其他组件很好的

Move_now的博客 873

SGISTL源码探究-STL中的红黑树(下)

前言在上一小节中,我们针对SGISTL中的红黑树的分析完成了一半,接下来的各种操作才是重点的部分。通过以下分析可以了解到红黑树是如何保持平衡的。(STL中实现红黑树定义的重载版本函数太多,我舍去了一部分定义,直接对其声明进行分析,不然篇幅太长)红黑树的常用操作clear上一小节中,析构函数调用了clear,它的实现如下 void clear() { if (node_count != 0)

Move_now的博客 872

SGISTL源码探究-关联式容器:multimap

前言在本小节中我们将分析multimap的实现。它和map的最大的不同之处在于,map不允许重复的键值插入,而multimap允许重复的键值插入,对应到底层的实现,multimap调用的是insert_equal进行插入操作,而map调用的是insert_unique进行插入操作。 另外还有一个不同之处是multimap不支持下标运算,这个也很合理,因为可能有重复键,访问下标时可能就对应了多个值,

Move_now的博客 863

SGISTL源码探究-关联式容器:multiset

前言multiset和set的实现十分相似,除了允许键值重复之外,几乎一样。而实现允许键值重复这一功能的是红黑树中提供的操作insert_equal,接下来我们就进入到它的源码,顺便也复习一下set,看看到底和set有什么不同。multiset的实现#ifndef __STL_LIMITED_DEFAULT_TEMPLATES template <class Key, class Compare =

Move_now的博客 847
上一篇: SGISTL源码探究-stl_algo.h中的基础算法
下一篇: SGISTL源码探究-stl_algo.h中的排列算法
UKey_
博客等级 码龄10年 258粉丝 186原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值