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,一经查实,立即删除!

相关文章

【Python函数】——sort,sorted

1、sorted和sort的常规使用 2、关于自定义比较函数 3、试验 from functools import cmp_to_key ll [(2,3,10),(1,2,3),(5,6,7),(2,5,10),(2,4,10)]# 根据一个维度进行排序&#xff0c;这里根据第一维排序 ll1 sorted(ll,key lambda x:x[0]) print(根据一个维度进行排序&a…

生成相关矩阵

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;直到发现到了不得不用的地步…

Lab01:Xv6 and Unix utilities

实验测试方法 实验的测试方法主要有2个&#xff1a; 进入到Xv6系统中&#xff0c;执行相应的命令使用实验提供的评分测试 对于单个实验&#xff0c;可以使用 make GRADEFLAGSapplication grade其中application为要测试的实验应用&#xff0c;例如sleep实验对应的评分测试命令…

jQuery学习- 位置选择器

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

数据类型之元组

存多个值&#xff0c;对比列表来说&#xff0c;元组不可变&#xff08;是可以当做字典的key的&#xff09;&#xff0c;主要是用来读 与列表类型比&#xff0c;只不过[]换成()age(11,22,33,44,55) #本质agetuple((11,22,33,44,55)) print(type(age)) age[0]12 t(1,2,[a,b]) pri…

cocos2d-x3.6 连连看连通画线

我的博客&#xff1a;http://blog.csdn.net/dawn_moon 网上看到非常多人写的连连看&#xff0c;都没有画连线的实现。事实上要话连线挺简单的。cocos2d-x 提供了一个非常方便的绘图形的类。DrawNode。这个类封装了非常多画线条&#xff0c;多边形的方法。非常方便&#xff0c;非…

阿里云大数据计算服务MaxCompute(上篇)

关于阿里云大数据计算服务MaxCompute的详细内容&#xff1a; 阿里云大数据计算服务MaxCompute使用教程 &#xff08;MaxCompute&#xff08;原ODPS&#xff09;是一项大数据计算服务&#xff0c;它能提供快速、完全托管的PB级数据仓库解决方案&#xff0c;使您可以经济并高效的…

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

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

【tensorFlow】——图像数据增强、读取图像、保存图像

#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2021/4/13 10:54 # @Author : @linlianqin # @Site : # @File : 数据增强(distorted).py # @Software: PyCharm # @description:一些基于TensorFlow的数据处理方法import tensorflow as tf import cv2 im…

数据分析方法有哪些_数据分析方法

数据分析方法有哪些_数据分析方法 随着大数据的到来&#xff0c;数据分析师成为大数据时代一颗冉冉升起的新星&#xff0c;现在企业越来越重视大数据&#xff0c;数据分析师这个职业也成为企业争抢的对象。那么数据分析师的分析数据的方法都有哪些呢&#xff1f; 1、数据分析遵…

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

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

记忆化搜索的应用

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

【深度学习】——DNN后向传播、CNN后向传播文章汇总

深度神经网络&#xff08;DNN&#xff09;模型与前向传播算法 深度神经网络&#xff08;DNN&#xff09;反向传播算法(BP) 卷积神经网络CNN的前向和后向传播&#xff08;一&#xff09; 卷积神经网络CNN的前向和后向传播&#xff08;二&#xff09; 有batch normalization的卷积…

ajaxReturn 之前dump调试,导致$.ajax不能正常运行

ajaxReturn 之前dump调试&#xff0c;导致$.ajax不能正常运行 以后调试的时候&#xff0c;注意下这个情况转载于:https://www.cnblogs.com/bushe/p/5180317.html

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

HDOJ5547 SudoKu

题目链接&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid5547 题目大意&#xff1a;填数独。。。 思路&#xff1a;爆搜 1 #include <stdio.h>2 #include <string.h>3 #include <iostream>4 #include <algorithm>5 using namespace std;6 boo…

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

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