C++ STL实现的优先队列( priority_queue )

本文参考的源码版本:gcc version 8.1.0 (x86_64-posix-seh-rev0, Built by MinGW-W64 project)。

priority_queue 本质是容器适配器,它对内部容器的元素有自己的管理方式,而 priority_queue 实际维护的是一个二叉堆。STL中 priority_queue 的操作是基于完全二叉树,使用随机访问迭代器访问元素,二叉堆在创建时按照层序遍历的顺序将数据放入容器中,因此创建 priority_queue 时使用的容器需要具有随机访问的特性。

priority_queue 是一个类模板,有三个模板参数,第一个数据的类型,第二个是容器的类型,默认为 vector ,第三个是用于比较操作的函数对象,默认是 less ,即小于比较。

template<typename _Tp, typename _Sequence = vector<_Tp>,typename _Compare  = less<typename _Sequence::value_type> >class priority_queue {};

完全二叉树

如果对满二叉树的结点进行编号, 约定编号从根结点起, 自上而下, 自左而右。则深度为 k 的, 有 n 个结点的二叉树, 当且仅当其每一个结点都与深度为 k 的满二叉树中编号从 1 至 n 的结点一一对应时, 称之为完全二叉树,换言之如果只删除了满二叉树最底层最右边的连续若干结点形成的树称为完全二叉树。满二叉树是完全二叉树的特列。完全二叉树具有以下特性:

  • 具有 n 个节点的完全二叉树的深度为:

k=[log2n]floor+1k = [log_2n]_{floor} + 1 k=[log2n]floor+1

  • iii 个结点的编号范围为: 1≤i≤n1 ≤ i ≤ n1in ;
  • 如果 i=1i = 1i=1iii 为根结点,无双亲;如果 i>1i > 1i>1 ,结点iii 的双亲结点为:

[i/2]floor[i/2]_{floor}[i/2]floor

  • 如果2i<=n2i<=n2i<=n ,结点i的左孩子为结点 2i2i2i;否则无左孩子

  • 如果 2i+1<=n2i+1<=n2i+1<=n,结点i的右孩子为结点 2i+12i+12i+1,否则无右孩子

  • 结点 iii 所在的层次为:

    ki=[log2i]floor+1k_i = [log_2i]_{floor} + 1 ki=[log2i]floor+1

  • 如果 i>1i > 1i>1, iii 为奇数时,结点 iii 为右子结点; iii 为偶数时,结点 iii 为左子结点

STL的 priority_queue 在实现的时候保证了堆顶的元素始终位于容器的第一个位置,相当于二叉堆的完全二叉树的位置是从 0 开始计数,与完全二叉树的计数有细微差别。

a7ee5383dc0877a65db01ce4030da6a7.png

堆有序化( reheapifying ) 中的上浮和下沉

当在二叉堆的最后一个位置插入新的元素时,新加入的元素可能会破坏堆的有序性,此时需要对新加结点与其父结点进行比较,如果大于父结点的值,那么就需要交换新加结点和父结点,如此重复比较,直到不再比父结点大时终止。这个过程就是堆有序化过程中的上浮操作。

当在移除二叉堆的堆顶元素时,被移除的元素破坏了堆的有序性,此时需要对堆顶的两个子结点中选择较大的值作为新的堆顶,而被选择的子结点则不能再作为子结点,则需继续比较其两个子结点,如此重复比较,直到到达堆底,或者两个子结点的值都比根结点小。这个过程就是堆有序化过程中的下沉操作。

#include <vector>
#include <random>
#include <chrono>using namespace std;class PriorityQueue {
public:void Push(int value) {v_.emplace_back(value);push_hole(v_.size() - 1,value);}void Pop() {int back = v_.back();int size = v_.size();int hole = 0;int right_child = 2 * (hole + 1); // 计算右孩子结点的位置while (right_child < size) {// 左孩子小于右孩子if (v_[right_child - 1] < v_[right_child]) {v_[hole] = v_[right_child];hole = right_child;} else { // 左孩子大于等于右孩子v_[hole] = v_[right_child - 1];hole = right_child - 1;}right_child = 2 * (hole + 1);}push_hole(hole, back);v_.pop_back(); // 最后才从容器中删除元素}int Top() const {return v_[0];};;int Empty() const {return v_.empty();}friend ostream &operator<<(ostream &os, const PriorityQueue &rhs) {for (const auto &v: rhs.v_) {os << v << " ";}os << endl;return os;}private:void push_hole(int hole,  int value) {int parent = (hole - 1) / 2; // 根据完全二叉树的特性计算父结点的位置// 父结点比新加的值小,交换父结点和新加入的值while (parent >= 0 && v_[parent] < value) {swap(v_[parent], v_[hole]);hole = parent;parent = (hole - 1) / 2;}v_[hole] = value;}private:vector<int> v_;
};int main() {PriorityQueue pq;default_random_engine  e(chrono::system_clock::now().time_since_epoch().count());uniform_int_distribution<int> d(1,1000);for (int i = 0; i < 100; i++) {pq.Push(d(e));}
//    vector<int> v{11,10,3,20,15,15,1,17,9,2,0,
//                  12,25,11,12,30,0,0,0,60,15,
//                  22,63,77,60,1,1,2,6,7,4,2,8};
//    for (int val : v) {
//        pq.Push(val);
//    }cout << "Container: " << pq;cout << "Top: ";while (!pq.Empty()) {int top = pq.Top();pq.Pop();cout << top << " ";}return 0;
}

priority_queue 的 push 操作

priority_queuepush 操作本质上就是二叉堆有序化的上浮,在真正的 push 前会先在容器的最后一个位置挖一个洞,以作为后续重排容器元素的中转位置。push 操作步骤如下:

  1. 先将要添加的数据添加到容器最后一个位置,相当于挖个洞;
  2. 然后对容器进行重排操作,重排操作的过程大致如下:
    • 初始时,洞的位置就是容器最后一个位置;
    • 如果容器本身是空的,那么在洞的位置添加新的元素后,该元素就堆顶的元素;
    • 如果容器本身不是空的,那么就通过洞的位置找到其父结点的位置,然后使用父结点的值与新加入的值进行比较,如果父结点的元素值小于新加入的值,那么就将父结点的值移到挖的洞中,这样中间位置就形成了一个洞,此时更新洞的位置。然后重复操作,直至洞的位置不再更新。
  3. 将新加入的值添加到洞中,完成一次 push 操作。

示例:

Push.gif

下面是 push 重排的关键源码部分,第一个参数是容器的首元素迭代器,第二个参数是洞的初始位置,第三个参数是堆顶,即容器第一个位置,第四个参数是新添加的值,第五个参数是用于进行比较操作的函数对象。从源码能够看出,在比较操作的是时候,父结点作为了运算符的左侧运算对象,父结点比新加的结点小才会进行相应的操作,也就不难理解为什么 less 比较操作维护的却是大堆顶。

template<typename _RandomAccessIterator,typename _Distance, typename _Tp,typename _Compare>
void __push_heap(_RandomAccessIterator __first,_Distance __holeIndex,_Distance __topIndex,_Tp __value,_Compare& __comp) {_Distance __parent = (__holeIndex - 1) / 2; // 计算父结点索引// 比较父结点和要push的值大小关系while (__holeIndex > __topIndex && __comp(__first + __parent, __value)) {*(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first + __parent));__holeIndex = __parent;__parent = (__holeIndex - 1) / 2;}// 将要push的值添加到洞里面*(__first + __holeIndex) = _GLIBCXX_MOVE(__value);}

priority_queue 的 pop 操作

priority_queuepush 操作本质上就是二叉堆有序化的下沉,pop 操作步骤如下:

  1. 首先记录容器最后一个位置的元素值;
  2. 将堆顶的元素移动到容器最后一个位置上,这样堆顶的位置就相当于形成了一个洞;
  3. 然后调整堆,调整堆的过程大致如下:
    • 首先根据堆顶找到其右孩子结点,然后比较右孩子结点和左孩子结点的值,将两者中较大的值放置到堆顶;
    • 被移走的数值则形成了一个洞,因此需要继续向下比较,直到最后不再更新洞的位置
  4. 将之前记录的最后一个位置的元素值 push 到最后一个孔的位置;
  5. 最后从容器中移除最后一个位置的元素。

示例:

Pop.gif

下面是 pop 调整堆的关键源码部分,第一个参数是容器的首元素迭代器,第二个参数是洞的初始位置,第三个参数是容器中除去要移除的元素后剩余的个数,第四个参数记录的pop前容器最后一个位置的元素,第五个参数是用于进行比较操作的函数对象。

template<typename _RandomAccessIterator, typename _Distance,typename _Tp, typename _Compare>
void __adjust_heap(_RandomAccessIterator __first,_Distance __holeIndex,_Distance __len,_Tp __value,_Compare __comp)
{const _Distance __topIndex = __holeIndex;_Distance __secondChild = __holeIndex;while (__secondChild < (__len - 1) / 2){// 计算某个父结点的右孩子结点索引__secondChild = 2 * (__secondChild + 1);// 比较左右两个孩子结点的大小,若左孩子结点大则更新索引if (__comp(__first + __secondChild,__first + (__secondChild - 1)))__secondChild--;*(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first + __secondChild));__holeIndex = __secondChild;}// 剩余元素个数为偶数,说明完全二叉树的最后一个位置不是右孩子结点,就只能是左孩子结点// 而如果此时的洞是最后一个位置的父结点,那么就只能将左孩子结点的值移动到父结点处。if ((__len & 1) == 0 && __secondChild == (__len - 2) / 2){__secondChild = 2 * (__secondChild + 1);*(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first+ (__secondChild - 1)));__holeIndex = __secondChild - 1;}__decltype(__gnu_cxx::__ops::__iter_comp_val(_GLIBCXX_MOVE(__comp)))__cmp(_GLIBCXX_MOVE(__comp));// 将之前记录的容器最后一个位置的值填入洞中std::__push_heap(__first, __holeIndex, __topIndex,_GLIBCXX_MOVE(__value), __cmp);
}

原文地址

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

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

相关文章

生成相关矩阵

U是X&#xff08;差异矩阵&#xff09;各列向量取方向后形成的矩阵&#xff0c;CU^T * U 即相关矩阵&#xff0c;即各列向量两两的夹角&#xff0c;&#xff08;夹角越小说明关联度越高&#xff09; clc avg_e66;avg_m66;avg_s76; x1[61 63 78 65 63] -avg_e; x2[53 73 61 84 5…

Java关于Properties用法的总结(一)

最近项目中有一个这样的需求&#xff0c;要做一个定时任务功能&#xff0c;定时备份数据库的操表&#xff0c;将表数据写入txt文件。因为文件的读写路径可能需要随时改动&#xff0c;所以写死或者写成静态变量都不方便&#xff0c;就考虑使用配置文件&#xff0c;这里总结些配置…

【tensorflow】——tensorboard可视化计算图以及参数曲线图loss图

参考文献&#xff1a; https://zhuanlan.zhihu.com/p/71328244 目录 1.可视化计算图 2.可视化参数 3. 远程tensorboard 4、报错 真是出来混迟早是要还的&#xff0c;之前一直拒绝学习Tensorboard&#xff0c;因为实在是有替代方案&#xff0c;直到发现到了不得不用的地步…

jQuery学习- 位置选择器

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>位置选择器</title><script src"js/jquery.js"></script><script type"text/javascript">$(function(){//获取第一个li$(&quo…

Vue3、TypeScript 实现图片数量及大小随宽度自适应调整

前言 过了这么久&#xff0c;想起自己还有个博客&#xff0c;更点内容吧&#xff01; 来&#xff0c;上需求&#xff01; 最近在做个前端界面&#xff0c;要求在一行中展示一些图片&#xff0c;展示的图片数量随着窗口宽度大小进行变化&#xff0c;除此之外还有以下要求&…

苹果Iphone/Ipad--L2T虚拟教程

1 Iphone和Ipad同为IOS&#xff0c;设置方法相同。首先进入IOS系统的“设置”程序。 2 点击“通用”进入通用设置&#xff0c;点击“”; 3 选择"添加设置 "&#xff1b; 4 选择L2TP方式&#xff0c;填写必要信息&#xff1a;描述、服务器地址 、您注册充值的账号及密…

记忆化搜索的应用

记忆化搜索的应用 一般来说&#xff0c;动态规划总要遍历所有的状态&#xff0c;而搜索可以排除一些无效状态。更重要的是搜索还可以剪枝&#xff0c;可能剪去大量不必要的状态&#xff0c;因此在空间开销上往往比动态规划要低很多。 如何协调好动态规划的高效率与高消费之间的…

Veebot-自动静脉抽血机器人

Veebot-自动静脉抽血机器人 我们可能都有过被抽血的经验。护士让你握紧拳头&#xff0c;用一根橡皮条压住你上臂的血管&#xff0c;在你的肘部内侧寻找你的静脉&#xff0c;有时还需要拍打几下&#xff0c;摸到隆起的静脉血管&#xff0c;一针下去。有时候碰到技术好的护士&…

idea 转普通项目为maven 项目

1、项目上右键 Add Framework Support。 2、选择maven&#xff0c;点击OK。 转载于:https://www.cnblogs.com/mayanze/p/8042489.html

【深度学习之ResNet】——深度残差网络—ResNet总结

目录 论文名称&#xff1a;Deep Residual Learning for Image Recognition 摘要&#xff1a; 1、引言 2、为什么会提出ResNet残差网络呢&#xff1f; 3、深度残差网络结构学习&#xff08;Deep Residual learning&#xff09; &#xff08;1&#xff09;残差单元 &#xf…

关于SafeMove White Paper功能

ABB机器人网站有一个 Safemove 功能的介绍&#xff0c;在Overview页面右半版有一篇文档是 SafeMove White Paper &#xff0c;在45页的 pdf 文档中&#xff0c;详细了介绍工业机器人的安全原则&#xff0c;以及ABB工业机器人自身 EPS (Electronic Position Switches) 和 SafeMo…

面试疑难点解析

List,Set,Map,有什么区别&#xff1f; List和Set实际上市实现了Collection接口&#xff0c;那么Collection接口的原理你能简单描述一下吗&#xff1f; List接口可以插入多个NULL值&#xff0c;并且重复值&#xff0c;而且LIST是一个有序的集合。 Set是一个不可重复的集合&#…

主机无法访问虚拟机的httpd服务

症状&#xff1a;虚拟机装的centos6.3 通过桥接的方式与主机连接 虚拟机通过yum安装httpd服务 在主机浏览器中输入 虚拟机ip 无法访问虚拟机Apache 虚拟机和主机可以相互ping通 解决&#xff1a;关掉虚拟机的防火墙就可以了 命令setup进入防火墙管理 按空格键取消防火墙启用 转…

2017-2018-1 20155229 《信息安全系统设计基础》第十三周学习总结

2017-2018-1 20155229 《信息安全系统设计基础》第十三周学习总结 对“第二章 信息的表示和处理”的深入学习 这周的任务是选一章认为最重要的进行学习&#xff0c;我选择了第二章。当今的计算机存储和处理信息基本上是由二进制&#xff08;位&#xff09;组成&#xff0c;二进…

【VOC格式xml文件解析】——Python

#!/usr/bin/env python # -*- coding: utf-8 -*- # Time : 2021/4/26 12:49 # Author : linlianqin # Site : # File : test1.py # Software: PyCharm # description: import xml.etree.ElementTree as ETdef xmli(xmlpath):xmlTree ET.parse(xmlpath) # 解析xml文…

win7系统的右键菜单只显示一个白色框不显示菜单项 解决办法

如上图所示&#xff0c;桌面或其他大部分地方点击右键菜单&#xff0c;都只显示一个白色框&#xff0c;鼠标移上去才有菜单项看&#xff0c;并且效果很丑 解决办法&#xff1a; 计算机—右键—属性—高级—性能—设置—视觉效果—淡入淡出或滑动菜单到视图&#xff0c;将其前面…

【setup.py编译出错】——提示无法查找到powershell.exe

https://www.cnblogs.com/wind-chaser/p/11359521.html pytorch fasterrcnn训练自己数据集文章链接 在进行faster rcnn pytorch跑通的时候遇到的&#xff0c;我是直接在pycharm中的终端上进行运行的&#xff0c;但是一直会跳出powershell.exe无法查找的错误&#xff0c; pytho…

同工不同酬,年薪 50 万美金的工程师到底有什么神本事?

同工不同酬&#xff0c;年薪 50 万美金的工程师到底有什么神本事&#xff1f;投递人 itwriter 发布于 2014-05-10 23:09 评论(6) 有6066人阅读 原文链接 [收藏] 英文原文&#xff1a;What kind of jobs do the software engineers who earn $500K a year do? 他们究竟是作…

艾里斑大小与像元尺寸的匹配问题

写给自己看的学习记录&#xff1a; 光具有波粒二象性&#xff0c;由此衍生出了几何光学与衍射光学。在光学设计软件中&#xff0c;最常用的判断标准是查看点列图的RMS半径以及MTF图的曲线&#xff0c;这两者分别代表了两种传播性质的评价方式。 在刚接触光学设计时&#xff0…

【mmdetection2.0错误】——ModuleNotFoundError: No module named ‘mmdet‘

一开始以为是安装包导入的相对路径的问题&#xff0c;结果鼓捣了一上午都没有用&#xff0c;最后才发现再进行mmdet2.0环境配置的时候忘记编译了 也就是如下语句&#xff1a; python setup.py develop