C++:stack_queue(2)实现底层

文章目录

  • 一.容器适配器
    • 1. 本质:
    • 2. 接口:
    • 3. 迭代器:
    • 4. 功能:
  • 二.deque的简单介绍
    • 1.概念与特性
    • 2.结构与底层逻辑
      • 2.1 双端队列(deque)结构:
      • 2.2 deque的内部结构
      • 2.3 deque的插入与删除操作:
    • 3.总结
      • 3.1 deque和list,vector对比
      • 3.2 常用vector或list,而不是deque
  • 三.仿函数初步
    • 1.仿函数的特点
    • 2.仿函数的基本实现
    • 3.仿函数,普通函数,函数指针
  • 四.优先队列(priority_queue)的简单介绍
    • 1.核心本质
    • 2.核心操作特点
    • 3.底层实现依赖
    • 4.关键接口(以你的代码+标准库逻辑为例)
    • 5.总结
  • 五.priority_queue的模拟实现
    • 1.头文件"priority_queue.h"
    • 2.测试文件"test.cpp"

一.容器适配器

在这里插入图片描述
容器适配器的核心就像交流电适配器:

  • 核心作用:对底层容器(如vectordeque)进行“包装”
  • 关键效果:让原本的容器以特定数据结构(栈、队列等)形式呈现
  • 核心价值:屏蔽底层复杂操作,只提供适配后结构的专属接口,精准满足特定场景的数据操作需求(如栈的“后进先出”、队列的“先进先出”)。

1. 本质:

封装底层容器,不直接存储数据

  • 容器适配器本身不实现数据存储逻辑,而是通过封装现有基础容器(如 vectordequelist 等)来实现功能。
  • 底层容器的选择可手动指定(需满足适配器的接口要求),若不指定则使用标准库默认的底层容器(如 stack 默认用 deque)。

2. 接口:

受限且专注于适配后的数据结构逻辑

  • 容器适配器仅提供适配后的数据结构的核心操作,屏蔽底层容器的复杂接口。
    例如:stack 仅提供 push()(栈顶插入)、pop()(栈顶删除)、top()(访问栈顶)等符合「后进先出(LIFO)」逻辑的操作;queue 仅提供 push()(队尾插入)、pop()(队头删除)等符合「先进先出(FIFO)」逻辑的操作。

3. 迭代器:

不支持迭代器访问,隐藏底层细节

  • 容器适配器不对外暴露迭代器,用户无法通过迭代器遍历适配器中的元素。
  • 这一设计的目的是严格限制数据操作方式,确保适配后的数据结构逻辑(如栈只能从栈顶操作)不被破坏。

4. 功能:

适配为特定数据结构

  • 容器适配器的核心作用是将底层容器「适配」为常用的数据结构,标准库提供三种典型适配器:
    • stack:适配为栈(LIFO 结构);
    • queue:适配为队列(FIFO 结构);
    • priority_queue:适配为优先队列(元素按优先级自动排序)。

二.deque的简单介绍

1.概念与特性

在这里插入图片描述
上图中展示的是双端队列(deque,Double-Ended Queue)的结构和操作方式。
双端队列是一种允许在队列两端进行插入和删除操作的线性数据结构,它结合了栈和队列的特性,支持在队列的前端(front)和后端(back)进行元素的添加(push)和移除(pop)操作。

在 C++ 标准库中,std::deque 是双端队列的实现,它具有以下特点:

优点:

  1. 动态扩展:双端队列的大小可以动态变化,支持在运行时根据需要扩展或收缩。
  2. 随机访问:虽然双端队列主要用于两端操作,但它也支持通过索引进行随机访问,这一点与 std::vector 类似。
  3. 高效操作:在双端队列的两端进行插入和删除操作的时间复杂度是 O(1),而在中间进行操作的时间复杂度是 O(n)。

缺点:

  1. 中间操作低效:在非两端位置进行插入/删除,需移动大量元素,时间复杂度为 O(n)。
  2. 内存碎片风险:由多段不连续内存块组成,频繁操作易产生内存碎片。
  3. 内存开销较高:需额外存储内存块管理信息,内存占用多于 vector
  4. 迭代器稳定性差:扩容或结构变动时,迭代器、指针、引用可能全部失效。

2.结构与底层逻辑

在这里插入图片描述

2.1 双端队列(deque)结构:

  1. 分段存储:由多段连续内存块组成,通过 map(指针数组)管理这些块,实现逻辑上的连续。
  2. 双向扩展:可从两端快速插入/删除(时间复杂度 O(1)),无需像 vector 那样整体扩容。
  3. map 管理map 存储内存块指针,当 map 满时需重新分配更大的 map 并复制指针(类似扩容)。
  4. 随机访问:通过 map 定位内存块,再通过偏移量访问元素,时间复杂度 O(1),但缓存局部性弱于 vector
  5. 中间操作劣势:在非两端位置插入/删除时,需移动元素,时间复杂度 O(n),效率低于链表。

在这里插入图片描述

2.2 deque的内部结构

以上图片展示的是C++标准库中双端队列(deque)的内部结构。以下是图中各部分的核心要点:

  1. 缓冲区(buffer):存储实际数据的连续内存块,多个缓冲区通过map管理。
  2. map:指针数组,每个指针指向一个缓冲区,实现逻辑上的连续。
  3. 迭代器(iterator):包含四个关键成员:
    • cur:指向当前元素的指针。
    • first:指向当前缓冲区起始位置的指针。
    • last:指向当前缓冲区末尾位置的指针。
    • node:指向map中当前缓冲区指针的指针。
  4. 中控器:管理map的分配与扩容,确保deque动态扩展的稳定性。
  5. 节点(node):map中的元素,每个节点对应一个缓冲区的指针。

这种结构使得deque能够高效地在两端进行插入和删除操作,同时支持随机访问。
在这里插入图片描述

2.3 deque的插入与删除操作:

  1. 插入操作
    • 两端插入:通过push_backpush_front实现,时间复杂度为O(1)。在对应端的缓冲区末尾或起始位置直接添加元素,若缓冲区已满则自动分配新缓冲区。
    • 中间插入:通过insert实现,时间复杂度为O(n)。需要移动插入位置之后的所有元素,效率较低。
  2. 删除操作
    • 两端删除:通过pop_backpop_front实现,时间复杂度为O(1)。直接移除对应端的元素,若缓冲区为空则释放该缓冲区。
    • 中间删除:通过erase实现,时间复杂度为O(n)。需要移动删除位置之后的所有元素,效率较低。
  3. 内存管理
    • 插入时若缓冲区满,会自动分配新缓冲区,并更新map中的指针。
    • 删除时若缓冲区为空,会自动释放该缓冲区,并更新map中的指针。

3.总结

3.1 deque和list,vector对比

deque 像是 vectorlist 的结合:

  • 兼具 vector 的随机访问能力(通过分段连续内存实现)和 list 的两端高效操作特性(O(1) 时间复杂度)。
  • 避免了 vector 头部操作低效和扩容成本高的问题,也缓解了 list 随机访问差、缓存友好性低的缺陷。
  • 但中间操作效率不及 list,随机访问性能略逊于 vector,是一种平衡型选择。

3.2 常用vector或list,而不是deque

多数情况选 vectorlist 而非 deque,因:

  1. 场景适配更精准:vector 强于随机访问,list 强于中间操作,覆盖多数需求;
  2. 性能更稳定:deque 随机访问略逊于 vector,中间操作远不如 list
  3. 更简单直观:vector/list 实现和特性更易理解,使用更稳妥。

三.仿函数初步

在C++中,仿函数(Functor) 是一种特殊的对象,它可以像函数一样被调用。从技术上讲,仿函数是实现了operator()重载的类或结构体的实例,这使得对象可以使用函数调用语法(如obj())来执行操作。

1.仿函数的特点

  1. 行为类似函数,但可以保存状态(成员变量)
  2. 可以像函数一样作为参数传递
  3. 比普通函数更灵活,因为可以携带额外数据
  4. 常用于STL算法中(如排序、查找等)

2.仿函数的基本实现

实现“小于比较”的仿函数功能

#include<iostream>
using namespace std;
namespace yl {// 定义模板类 less,用于实现“小于比较”的仿函数功能template<class T>class less{public:bool operator()(const T& x, const T& y){return x < y;}};
}int main(){yl::less<int> lessfunc;
cout << lessfunc(1, 2)<<endl;return 0;
}

仿函数优化冒泡排序

#include <iostream>
#include <vector>
using namespace std;// 仿函数:升序比较
template <typename T>
struct Less {bool operator()(const T& a, const T& b) const {return a < b; }
};// 仿函数:降序比较
template <typename T>
struct Greater {bool operator()(const T& a, const T& b) const {return a > b; }
};// 通用冒泡排序函数,通过仿函数定制比较规则
template <typename T, typename Compare>
void bubbleSort(vector<T>& arr, Compare comp) {int n = arr.size();for (int i = 0; i < n - 1; ++i) {for (int j = 0; j < n - i - 1; ++j) {// 调用仿函数的()运算符进行比较if (comp(arr[j], arr[j + 1])) { swap(arr[j], arr[j + 1]);}}}
}int main() {vector<int> arr = {64, 34, 25, 12, 22, 11, 90};// 用Less仿函数,升序排序bubbleSort(arr, Less<int>());cout << "升序排序结果:";for (int num : arr) {cout << num << " ";}cout << endl;// 重置数组arr = {64, 34, 25, 12, 22, 11, 90};// 用Greater仿函数,降序排序bubbleSort(arr, Greater<int>());cout << "降序排序结果:";for (int num : arr) {cout << num << " ";}cout << endl;return 0;
}

3.仿函数,普通函数,函数指针

对比维度普通函数函数指针仿函数
本质一段可执行代码块指向函数的指针变量(内存地址)重载了()运算符的类/结构体实例
状态管理无状态,依赖参数/全局变量无状态,依赖所指函数的状态管理通过成员变量保存状态,每个实例独立
调用方式直接通过函数名调用通过指针变量间接调用像函数一样调用对象(obj()
行为灵活性逻辑固定,修改需重写函数可指向不同函数改变行为可通过构造函数配置行为,同类型可有不同实例
作为参数传递可直接传递函数名传递指针变量传递对象实例
类型信息仅函数签名仅表示函数签名自身是类类型,携带完整类型信息
泛型编程适配性适配性一般模板中适配性差,难优化易被模板推导,编译器可内联优化
扩展性扩展需新增函数扩展依赖新增函数并重新指向可通过继承等面向对象特性扩展

总结:

  1. 普通函数

    • 核心价值:最基础的可调用单元,语法简单直接
    • 关键局限:行为完全固定,无法动态调整或携带状态
  2. 函数指针

    • 核心价值:通过指向不同函数,实现行为动态切换(解耦调用者与具体实现)
    • 关键局限:仍无状态管理能力,本质是"函数地址的容器"
  3. 仿函数

    • 核心突破:同时具备函数调用能力与对象状态管理(通过成员变量保存上下文)
    • 关键优势:在泛型编程中表现最优——既能像函数一样被调用,又能通过对象属性定制行为,且易被编译器优化

简言之:普通函数是"固定行为",函数指针是"可变行为但无记忆",仿函数是"可变行为且有记忆"。

四.优先队列(priority_queue)的简单介绍

1.核心本质

  • 容器适配器,基于“严格弱排序”规则维护元素,默认保证队首(堆顶)始终是当前队列里“最大元素”(大根堆特性 ,也可通过仿函数改小根堆),逻辑类似堆结构

2.核心操作特点

  • 支持插入元素(随时往队列里加元素),但只能访问/删除堆顶(最大/指定规则的优先级最高元素) ,类似堆的 top 访问、push 插入、pop 弹出逻辑

3.底层实现依赖

  • 基于容器适配器封装底层容器(如 vector/deque,默认用 vector ),需底层容器支持:
    • 基础操作:empty()(判空)、size()(元素个数)、push_back()(尾插)、pop_back()(尾删 ,但优先队列逻辑上是从“堆顶”删,实际通过交换+尾删实现)
    • 迭代器要求:需随机访问迭代器(像 vector/dequelist 是双向迭代器,不满足默认 priority_queue 需求,你自定义 queuelist 是因为自己封装逻辑没严格限制迭代器 )

4.关键接口(以你的代码+标准库逻辑为例)

接口功能说明
priority_queue()构造空优先队列
priority_queue(first, last)用迭代器区间 [first, last) 初始化,内部自动建堆
empty()判断是否为空
top()返回堆顶元素(最大/自定义优先级最高)
push(x)插入元素 x,插入后会调整堆结构维持优先级规则
pop()删除堆顶元素(实际先交换堆顶和尾元素,删尾元素,再调整堆结构)

5.总结

简单说,标准库 priority_queue 就是“自动维护堆结构的适配器”,借助底层容器+堆调整函数,让用户方便用“堆”的特性(始终访问优先级最高元素),你代码里自己实现的 priority_queue 也复刻了这个思路,只是死循环问题导致运行异常,修复后就能正常体现优先队列(大根堆)逻辑

五.priority_queue的模拟实现

1.头文件"priority_queue.h"

#pragma once
#include<iostream>
#include<functional>
#include<vector>
#include<cassert>
using namespace std;namespace yl
{template<typename T,typename container=vector<T>>class priority_queue{private://子结点中有一个大于父结点就可以向下调整void adjust_down(size_t parent) {size_t child = parent * 2 + 1;  // 左孩子初始位置while (child < _con.size()) {// 先找到左右孩子中值较大的那个if (child + 1 < _con.size() && _con[child + 1] > _con[child]) {child++;  // 右孩子更大,指向右孩子}// 如果最大的孩子大于父节点,交换并继续向下调整if (_con[child] > _con[parent]) {swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else {// 父节点大于等于子节点,堆性质已满足,退出循环break;}}}// 向上调整函数:用于维护大根堆性质(当插入新元素时)// 从指定子节点开始,向上与父节点比较并交换,直到满足堆条件void adjust_up(size_t child){// 计算父节点索引(堆的父节点计算公式)size_t parent = (child - 1) / 2;// 当子节点索引大于0时(未到达根节点),继续调整while (child > 0){// 若子节点值大于父节点值,违反大根堆性质,需要交换if (_con[child] > _con[parent]){swap(_con[child], _con[parent]);// 更新子节点为原父节点位置,继续向上调整child = parent;// 计算新的父节点索引parent = (parent - 1) / 2;}else{// 子节点值小于等于父节点,已满足大根堆性质,退出调整break;}}}public:priority_queue() = default;//强制生成默认构造template<typename InputIterator>priority_queue(InputIterator first,InputIterator last):_con(first,last){//向下调整算法建堆for (size_t i = (_con.size() - 2) / 2; i < _con.size() && i >= 0; i--){//i < _con.size() && i >= 0 作为循环条件://i < _con.size() 防止 i 越界(理论上 i 从(size - 2) / 2 递减到 0,不会越界,但加上更安全)。// i >= 0 利用 size_t 的无符号特性:// 当 i 减到 0 后,再执行 --i 会变成 SIZE_MAX(一个极大的无符号数),此时 i >= 0 会失效,循环终止。adjust_down(i);}}// 插入元素void push(const T& x){_con.push_back(x);// 对新插入的元素(位于末尾)进行向上调整,维护堆性质adjust_up(_con.size() - 1);}// 从堆中删除顶部元素(堆顶为最大值)void pop(){// 交换堆顶元素与最后一个元素(便于后续删除最后一个元素)swap(_con[0], _con[_con.size() - 1]);  _con.pop_back();// 对新的堆顶元素进行向下调整,维护堆性质adjust_down(0);}// 获取最大值(这里建的是大堆)const T& top()const{return _con[0];}// 获取元素的个数size_t size(){return _con.size();}bool empty() const{return _con.empty();}private:container _con;};
}

2.测试文件"test.cpp"

#include"priority_queue.h"void test01()
{vector<int> v = { 1,23,14,51,66,73,29,30 };yl::priority_queue<int,vector<int>> pq(v.begin(),v.end());while (!pq.empty()){cout << pq.top() << " ";pq.pop();}cout << endl;
}
int main()
{test01();return 0;
}

输出结果如下:
在这里插入图片描述

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

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

相关文章

Lightroom 安卓版 + Windows 版 + Mac 版全适配,编辑管理一站式,专业摄影后期教程

软件是啥样的​ Adobe Lightroom 这软件&#xff0c;在安卓手机、Windows 电脑和 Mac 电脑上都能用。不管是喜欢拍照的人&#xff0c;还是专门搞摄影的&#xff0c;用它都挺方便&#xff0c;能一站式搞定照片编辑、整理和分享这些事儿。 ****下载地址 分享文件&#xff1a;【Li…

office卸载不干净?Office356卸载不干净,office强力卸载软件下载

微软官方认可的卸载工具&#xff0c;支持彻底清除Office组件及注册表残留。需要以管理员身份运行&#xff0c;选择“移除Office”功能并确认操作。 Office Tool Plus安装地址获取 点击这里获取&#xff1a;Office Tool Plus 1、双击打开软件 image 2、选择左右的工具箱&…

互联网企业慢性死亡的招聘视角分析:从岗位割裂看战略短视

内容简介&#xff1a; 一个猎头和HR的简单拒绝&#xff0c;揭示了中国互联网企业人才观念的深层问题。通过分析岗位过度细分现象&#xff0c;本文探讨了战略短视、内斗文化和核心竞争力缺失如何导致企业慢性死亡&#xff0c;并提出了系统性的解决方案。#互联网企业 #人才招聘 #…

OpenBMC中phosphor-dbus-interfaces深度解析:架构、原理与应用实践

引言 在OpenBMC生态系统中&#xff0c;phosphor-dbus-interfaces作为D-Bus接口定义的核心组件&#xff0c;扮演着系统各模块间通信"契约"的关键角色。本文将基于OpenBMC源码&#xff0c;从架构设计、实现原理到实际应用三个维度&#xff0c;全面剖析这一基础组件的技…

驾驶场景玩手机识别准确率↑32%:陌讯动态特征融合算法实战解析

原创声明本文为原创技术解析文章&#xff0c;核心技术参数与架构设计参考自《陌讯技术白皮书》&#xff0c;转载请注明出处。一、行业痛点&#xff1a;驾驶场景行为识别的现实挑战根据交通运输部道路运输司发布的《驾驶员不安全行为研究报告》显示&#xff0c;驾驶过程中使用手…

Mysql——单表最多数据量多少需要分表

目录 一、MySql单表最多数据量多少需要分表 1.1、阿里开发公约 1.2、一个三层的B+树,它最多可以存储多少数据量 1.3、示例 1.3.1、示例表中一行的数据占多少字节数 1.3.2、示例表中一页里面最多可以存多少条记录 1.3.3、按示例表计算,一个三层的B+树,可以放多少条100字节的数…

scikit-learn/sklearn学习|岭回归解读

【1】引言 前序学习进程中&#xff0c;对用scikit-learn表达线性回归进行了初步解读。 线性回归能够将因变量yyy表达成由自变量xxx、线性系数矩阵www和截距bbb组成的线性函数式&#xff1a; y∑i1nwi⋅xibwTxby\sum_{i1}^{n}w_{i}\cdot x_{i}bw^T{x}byi1∑n​wi​⋅xi​bwTxb实…

基于Django的图书馆管理系统的设计与实现

基于Django的图书馆管理系统的设计与实现、

ComfyUI版本更新---解决ComfyUI的节点不兼容问题

前言&#xff1a; 新版本的COMFYUI与节点容易出现不兼容的问题,会导致整个系统崩掉。 目录 一、前期准备工作&#xff1a;虚拟环境配置 为什么需要虚拟环境&#xff1f; 具体操作步骤 二、常见问题解决方案 1、工作流输入输出图像不显示问题 2、工作流不能拖动&#xff0…

生产管理ERP系统|物联及生产管理ERP系统|基于SprinBoot+vue的制造装备物联及生产管理ERP系统设计与实现(源码+数据库+文档)

生产管理ERP系统 目录 基于SprinBootvue的制造装备物联及生产管理ERP系统设计与实现 一、前言 二、系统设计 三、系统功能设计 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕…

Numpy科学计算与数据分析:Numpy数组创建与应用入门

Numpy数组创建实战 学习目标 通过本课程的学习&#xff0c;学员将掌握使用Numpy库创建不同类型的数组的方法&#xff0c;包括一维数组、多维数组、全零数组、全一阵列、空数组等。本课程将通过理论讲解与实践操作相结合的方式&#xff0c;帮助学员深入理解Numpy数组的创建过程…

如何回收内存对象,有哪些回收算法?

它的主要不足有两个&#xff1a; 效率问题&#xff0c;标记和清除两个过程的效率都不高。 空间问题&#xff0c;标记清除之后会产生大量不连续的内存碎片&#xff0c;空间碎片太多可能会导致以后在程序运行过程中需 要分配较大对象时&#xff0c;无法找到足够的连续内存而不得不…

Numpy科学计算与数据分析:Numpy文件操作入门之数组数据的读取和保存

Numpy文件读写实战 学习目标 通过本课程&#xff0c;学员将深入了解如何使用Numpy库进行数组数据的读取和保存&#xff0c;包括文本文件和二进制文件的处理方法。通过本课程的学习&#xff0c;学员将能够熟练掌握Numpy在文件操作中的应用&#xff0c;为数据处理和分析打下坚实…

AutoMQ-Kafka的替代方案实战

AutoMQ无缝兼容kafka&#xff0c;并且借助S3实现数据统一存储。这个确实解决了大问题&#xff01; 1. Kafka的挑战 横向扩展困难&#xff1a;扩容kafka需要手动创建分区迁移策略和复制分区数据。这个过程不仅风险高、资源密集而且耗时。存储成本高&#xff1a;计算和存储在kaf…

Numpy科学计算与数据分析:Numpy线性代数基础与实践

Numpy线性代数实践&#xff1a;从矩阵乘法到特征值 学习目标 通过本课程&#xff0c;学员将掌握Numpy中处理线性代数问题的基本方法&#xff0c;包括矩阵乘法、求解线性方程组以及计算特征值和特征向量。本课程将通过理论与实践相结合的方式&#xff0c;帮助学员深入理解这些…

CrackMapExec--安装、使用

用途限制声明&#xff0c;本文仅用于网络安全技术研究、教育与知识分享。文中涉及的渗透测试方法与工具&#xff0c;严禁用于未经授权的网络攻击、数据窃取或任何违法活动。任何因不当使用本文内容导致的法律后果&#xff0c;作者及发布平台不承担任何责任。渗透测试涉及复杂技…

深入理解模板方法模式:框架设计的“骨架”艺术

目录 前言 一、模板方法模式的核心思想 二、模板方法模式的结构组成 1. 抽象类&#xff08;Abstract Class&#xff09; 2. 具体子类&#xff08;Concrete Class&#xff09; 三、C 实现示例&#xff1a;咖啡与茶的制作流程 步骤 1&#xff1a;定义抽象类&#xff08;饮料…

LinkedList 深度解析:核心原理与实践

文章目录 一、底层数据结构与特性 1. 核心数据结构 2. 关键特性 二、核心操作机制解析 1. 添加元素机制 2. 删除元素机制 三、性能关键点分析 1. 时间复杂度对比 2. 空间开销 四、线程安全解决方案 1. 同步包装器 2. 使用并发集合 五、经典面试题解析 1. ArrayList 和 LinkedLi…

Jmeter性能测试之安装及启动Jmeter

1. 安装JDK Jmeter依赖JDK环境,如果电脑没有JDK,需要安装JDK.如下是Jmeter版本与JDK版本对应关系. 2. Jmeter下载安装 下载链接&#xff1a;https://archive.apache.org/dist/jmeter/binaries/ windows下载.zip压缩包Linux下载.tar压缩包 下一步下一步就行 3. 配置环境变…

ShadowKV 机制深度解析:高吞吐长上下文 LLM 推理的 KV 缓存“影子”方案

背景与核心思想简介 在LLM的长上下文推理中&#xff0c;KV Cache成为影响速度和内存的关键因素。每生成一个新token&#xff0c;模型需要对所有先前token的键&#xff08;Key&#xff09;和值&#xff08;Value&#xff09;向量执行自注意力计算。传统方法会将所有过去的K/V向量…