当前位置: 首页 > news >正文

【C++】优先级队列(容器适配器)

欢迎来到我的Blog,点击关注哦💕

前言

string vector list 这种线性结构是最基础的存储结构,C++(STL)container很好的帮助我们数据存储的问题。

容器适配器

介绍

  • 容器适配器是C++标准模板库(STL)中的一种设计模式,它允许将一个容器的接口转换为另一个接口,从而提供不同的操作和行为。
  • 容器适配器通常用于封装现有容器,以实现特定的数据结构特性,如栈(后进先出)、队列(先进先出)和优先队列(根据优先级排序)。

应用

  • 栈(stack):栈是一种后进先出的数据结构,其操作包括入栈(push)、出栈(pop)、查看栈顶元素(top)等。栈适配器可以基于多种底层容器实现,如vectordequelist.

  • 队列(queue):队列是一种先进先出的数据结构,其操作包括入队(push)、出队(pop)、查看队首元素(front)和查看队尾元素(back)。队列适配器同样可以基于dequelist实现,以适应不同的性能需求.

  • 优先队列(priority_queue):优先队列是一种特殊的队列,它根据元素的优先级进行排序。其底层容器通常是vectordeque,并通过堆算法维护元素的优先级顺序。优先队列适配器提供了插入和删除具有最高优先级元素的操作.

双重结束队列(双端队列(deque))

特点

  • 双端操作效率:支持在两端进行快速的插入和删除操作。
  • 随机访问:可以通过索引直接访问容器中的元素。
  • 无需预先分配固定大小:与vector不同,deque不需要在创建时指定大小,它可以根据需要动态增长。
  • 内存分配策略deque不需要像vector那样一次性分配大量内存,而是分散在内存中,这有助于减少内存碎片。

存储结构

双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落 在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Listvector deque对比

对比维度VectorDequeList
内存连续性
随机访问性能O(1)O(1) 但可能不如VectorO(n)
插入/删除性能非末尾O(n)两端O(1), 中间O(n)两端及中间O(1)
内存重用效率扩容时需移动元素两端添加删除不需移动不适用
内存分配模式动态数组,连续内存分段连续内存非连续内存
迭代器失效可能不会不会
支持的操作[] 访问、.at() 等[] 访问、.at() 等[] 访问、.at() 等
内存管理开销高(扩容时)中等(两端操作)
适用场景需要快速随机访问且元素数量稳定需要两端快速插入删除,随机访问需求适中频繁插入删除,不关心随机访问

栈(stack)

栈的介绍

函数说明接口说明
stack()构造空的栈
empty()检测stack是否为空
size()返回stack中元素的个数
top()返回栈顶元素的引用
push()将元素val压入stack中
pop()将stack中尾部的元素弹出

栈的模拟实现

利用容器适配器的设计原理,很容易实现

  • 将栈放mystack的命名空间,以防止和库中冲突
  • 类模板设计container可以给缺省参数,默认deque(容器适配器)
  • 在里面利用deque的接口实现
namespace mystack
{template<class T, class Container = std::deque<T>>class stack{public:void push_back(const T& x){_con.push_back(x);}void pop(){_con.pop_back();}size_t size(){return _con.size();}T& top(){return _con.back();}bool empty(){return _con.empty();}private:Container _con;};
}

队列

队列介绍

函数声明接口说明
queue()构造空的队列
empty()检测队列是否为空,是返回true,否则返回false
size()返回队列中有效元素的个数
front()返回队头元素的引用
back()返回队尾元素的引用
push()在队尾将元素val入队列
pop()将队头元素出队列

队列模拟实现

  • 将栈放myqueue的命名空间,以防止和库中冲突
  • 类模板设计container可以给缺省参数,默认deque(容器适配器)
  • 在里面利用deque的接口实现
namespace myqueue
{template<class T, class Container = std::deque<T >>class queue{public:void push(const T& x){_con.push_back(x);}void pop(){_con.pop_front();}size_t size(){return _con.size();}T& front(){return _con.front();}bool empty(){return _con.empty();}private:Container _con;};
}

优先级队列(priority_queue)

基本原理

  • 优先级队列通常在内部使用堆数据结构来维护元素的优先级。
  • 堆是一种完全二叉树,可以是最大堆或最小堆。
  • 在最大堆中,父节点的值总是大于或等于其子节点的值,而在最小堆中,父节点的值总是小于或等于其子节点的值。
  • 插入操作通过在堆的适当位置插入新元素并进行上调整(heapify-up)来维持堆的性质。
  • 删除操作则涉及到移除堆顶元素(优先级最高的元素)并进行下调整(heapify-down),以恢复堆的结构。

priority_queue介绍

函数声明接口说明
priority_queue()/priority_queue(first, last)构造一个空的优先级队列
empty( )检测优先级队列是否为空,是返回true,否则返回 false
top( )返回优先级队列中最大(最小元素),即堆顶元素
push(x)在优先级队列中插入元素x
pop()删除优先级队列中最大(最小)元素,即堆顶元素

优先级模拟实现 (可以参考)

仿函数

  • 仿函数(Functor)是C++中的一个编程概念,它指的是一个类或结构体,通过重载函数调用运算符operator(),使得这个类或结构体的对象可以像函数一样被调用。
  • 仿函数可以包含状态,因为它们是对象,可以在构造函数中初始化状态,并在operator()中使用该状态。
  • 仿函数可以作为参数传递给其他函数,包括STL算法中的函数,从而提供灵活的编程模型.

这个就是一个仿函数

//小于
template<class T>class Less{public:bool operator()(const T& a,const T& b){return a < b;}};
//大于
template<class T>class Greater{public:bool operator()(const T& x, const T& y){return x > y;}};

priority_queue两个关键

向下建堆

  • 确定起始点:从最后一个非叶子节点开始向下建堆,这个节点也被称为堆的最后一个非叶子节点。在完全二叉树中,最后一个非叶子节点的索引可以通过 (n - 1 - 1) / 2 计算得到,其中 n 是数组的长度。
  • 执行向下调整:对每个非叶子节点执行向下调整操作,确保该节点与其子节点组成的子树满足堆的性质。向下调整的过程涉及到与子节点的比较和必要时的交换,直至到达堆的顶部或直到父节点不再违反堆的性质。
  • 迭代过程:从最后一个非叶子节点开始,逐步向上调整,直到根节点。每次调整后,更新当前节点的索引,以便进行下一次调整。
  • 完成建堆:重复步骤2和步骤3,直到根节点也满足堆的性质,此时整个数组就构建成了一个堆。
void AdjustDown(size_t parent)
{compare com;//仿函数size_t child = parent * 2 + 1;//if (child+1< _con.size() && _con[child] < _con[1+child])if (child + 1 < _con.size() && com(_con[child] ,_con[1 + child]))//和上面等价{++child;}while (child <_con.size()){if (com(_con[parent], _con[child])){std::swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

向上建堆

  • 初始化堆大小**:设置堆的大小为数组的大小,即 n
  • 从最后一个非叶子节点开始向上调整:在完全二叉树中,最后一个非叶子节点的索引为 floor((n - 1) / 2)。从这个节点开始向上调整,确保每个节点都满足大根堆的性质。
  • 执行向上调整操作:对于每个非叶子节点,检查其与子节点的关系,并进行必要的交换,以确保父节点的值大于或等于其子节点的值。如果子节点中有一个或两个,选择较大的子节点与父节点进行比较。如果父节点的值小于子节点的值,交换它们的位置,并重新设置父节点为当前子节点,继续向上调整。
  • 重复步骤2和3:直到达到根节点,即堆的第一个元素。
void AdjustUp(int child)
{compare com;int parent = (child - 1) / 2;while (child > 0){if (com(_con[parent] , _con[child])){std::swap(_con[parent], _con[child]);child = parent;parent = (child - 1) / 2;}else{break;}}}

初始化数据

迭代器初始化

  • 模板嵌套给,迭代器初始化

  • 依次push数据,在进行堆的建立

template<class InputIterator>
void push_back(InputIterator first, InputIterator last){while (first != last){_con.push_back(*first);++first;}//向下建堆for (int i = (_con.size() - 2) / 2; i >= 0; i--){AdjustDown(i);}}

pop数据

  • 将一个个数据和最后一个数据进行交换(目的:保持当前堆的结构)
  • pop出数据,将第一个数据进行向下调整
void pop()
{swap(_con[0], _con[_con.size() - 1]);_con.pop_back();AdjustDown(0);
}

push数据

  • 将数据进行尾插入,进行向上调整
void push(const T& x)
{_con.push_back(x);AdjustUp(_con.size() - 1);
}

priority_queue operators

  • top数据,返回首个数据;
  • 其他常见操作,采取容器适配器设计模式的操作
namespace mypriority_queue
{template<class T, class container = std::vector<T>,class compare = Less<T>>class priority_queue{public:const T& top(){return _con[0];}size_t szie(){return _con.size();}bool empty(){return _con.empty();}private:container _con;};}

源码(优先级队列)

namespace mypriority_queue
{template<class T>class Less{public:bool operator()(const T& a,const T& b){return a < b;}};template<class T>class Greater{public:bool operator()(const T& x, const T& y){return x > y;}};template<class T, class container = std::vector<T>,class compare = Less<T>>class priority_queue{void AdjustDown(size_t parent){compare com;size_t child = parent * 2 + 1;//if (child+1< _con.size() && _con[child] < _con[1+child])if (child + 1 < _con.size() && com(_con[child] ,_con[1 + child])){++child;}while (child <_con.size()){if (com(_con[parent], _con[child])){std::swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}void AdjustUp(int child){compare com;int parent = (child - 1) / 2;while (child > 0){if (com(_con[parent] , _con[child])){std::swap(_con[parent], _con[child]);child = parent;parent = (child - 1) / 2;}else{break;}}}public:template<class InputIterator>void push_back(InputIterator first, InputIterator last){while (first != last){_con.push_back(*first);++first;}//向下建堆for (int i = (_con.size() - 2) / 2; i >= 0; i--){AdjustDown(i);}}void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();AdjustDown(0);}const T& top(){return _con[0];}void push(const T& x){_con.push_back(x);AdjustUp(_con.size() - 1);}size_t szie(){return _con.size();}bool empty(){return _con.empty();}private:container _con;};}

向下建堆
for (int i = (_con.size() - 2) / 2; i >= 0; i–)
{
AdjustDown(i);
}
}

	void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();AdjustDown(0);}const T& top(){return _con[0];}void push(const T& x){_con.push_back(x);AdjustUp(_con.size() - 1);}size_t szie(){return _con.size();}bool empty(){return _con.empty();}private:container _con;
};

}


http://www.lryc.cn/news/418807.html

相关文章:

  • docker代理
  • (四)activit5.23.0修复跟踪高亮显示BUG
  • AsyncTask
  • 嵌入式面试知识点总结 -- FreeRTOS篇
  • 【深度学习】注意力机制(Transformer)
  • 【MySQL】将一张表的某一个值赋值到另一张表中
  • 怎样确定局域网里面是否有MAC地址冲突
  • springboot 大学生兼职平台系统-计算机毕业设计源码05282
  • CentOS linux安装nginx
  • 事务性邮件接口API如何集成以实现自动化?
  • zabbix 监控软件
  • C语言随机数小游戏
  • 解决Ubuntu报“无法解析域名cn.archive.ubuntu.com“问题
  • 搭建pxe网络安装环境实现服务器自动部署
  • Go框架选战:Gin、Echo、Fiber的终极较量
  • 2024.8.08(python)
  • RabbitMQ知识总结(基本原理+高级特性)
  • 字符串切割split
  • Python中的 `continue` 语句:掌握循环控制的艺术
  • AI安全新纪元:智能体驱动的网络安全新范式
  • c语言学习,isascii()函数分析
  • DAMA学习笔记(十二)-数据质量
  • 查找项目中丢失脚本的物体
  • 支付宝开放平台竟出现一张神秘人脸!
  • 每日学术速递8.8
  • 【JVM基础12】——垃圾回收-说一下JVM有哪些垃圾回收器?
  • 进阶学习------linux运维读写执行权限
  • 视频循环存储的实现
  • 在centOS系统中使用docker部署Jenkins
  • Qt 将生成的exe文件自动复制到其它目录下