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

C++之容器适配器介绍 以及 STL--stack queue deque

stack queue

    • 容器适配器
      • 1. `stack`(栈)
      • 2. `queue`(队列)
      • 3. `priority_queue`(优先队列)
      • 底层容器选择
      • 总结
    • 一、C++ Stack 介绍
      • (一)定义
      • (二)主要操作
      • (三)底层容器
    • 二、C++ Stack 的使用
      • (一)包含头文件
      • (二)基本操作示例
      • (三)使用自定义类型
      • (四)底层容器的选择
    • 一、C++ Queue 介绍
      • (一)定义
      • (二)主要操作
      • (三)底层容器
    • 二、C++ Queue 的使用
      • (一)包含头文件
      • (二)基本操作示例
      • (三)使用自定义类型
      • (四)底层容器的选择
    • 三、C++ Priority Queue(优先队列)
      • (一)定义
      • (二)主要操作
      • (三)自定义优先级
    • deque简要介绍
      • 特点
      • deque的缺陷
      • 为什么选择deque作为stack和queue的底层默认容器

在这里插入图片描述

容器适配器

在C++中,容器适配器(Container Adaptors)是一种特殊的容器类,它们提供了特定的接口来操作底层容器。容器适配器本身并不直接存储元素,而是通过封装其他容器(如vectordequelist)来提供特定的功能。C++标准库提供了三种主要的容器适配器:stack(栈)、queue(队列)和priority_queue(优先队列)。

1. stack(栈)

  • 特点:后进先出(LIFO)的数据结构。
  • 支持操作push(入栈)、pop(出栈)、top(访问栈顶元素)、empty(判断是否为空)、size(返回元素个数)。
  • 底层容器:默认使用deque,也可指定为vectorlist。可在第二个参数给出。

2. queue(队列)

  • 特点:先进先出(FIFO)的数据结构。
  • 支持操作push(入队)、pop(出队)、front(访问队首元素)、back(访问队尾元素)、emptysize
  • 底层容器:默认使用deque,也可指定为list

3. priority_queue(优先队列)

  • 特点:元素按优先级排序,优先级高的元素先出队。
  • 支持操作push(插入元素)、pop(删除优先级最高的元素)、top(访问优先级最高的元素)、emptysize
  • 底层容器:默认使用vector,结合heap算法实现。
  • 默认排序:最大堆(大顶堆),即元素按降序排列。

底层容器选择

容器适配器可通过模板参数指定底层容器:

// 使用vector作为stack的底层容器
stack<int, vector<int>> stackWithVector;// 使用list作为queue的底层容器
queue<int, list<int>> queueWithList;

总结

容器适配器数据结构特点默认底层容器适用场景
stackLIFOdeque递归模拟、表达式求值
queueFIFOdeque任务调度、广度优先搜索
priority_queue优先级排序vector+heap任务调度(按优先级)、贪心算法

选择合适的容器适配器可以提高代码的可读性和性能。

一、C++ Stack 介绍

(一)定义

在 C++ 中,stack 是一种容器适配器,它提供了一种后进先出(Last In First Out,LIFO)的数据结构。它基于底层容器(默认是 std::deque,也可以是 std::vectorstd::list 等)来存储元素,但只允许在容器的一端(称为栈顶)进行操作。

(二)主要操作

  1. 构造和析构
    • 默认构造函数会创建一个空的栈。
    • 析构函数会销毁栈中的所有元素。
  2. 元素访问
    • top():返回栈顶元素的引用。如果栈为空,调用 top() 是未定义行为。
  3. 容量
    • empty():检查栈是否为空,如果为空返回 true,否则返回 false
    • size():返回栈中元素的数量。
  4. 修改器
    • push(const T& value):将一个新元素压入栈顶。
    • pop():移除栈顶元素。注意,pop() 不返回被移除的元素,如果栈为空,调用 pop() 是未定义行为。
    • emplace(Args&&... args):在栈顶构造一个新元素,避免了额外的拷贝或移动操作。

(三)底层容器

stack 的底层容器默认是 std::deque,但可以通过模板参数指定其他容器,如 std::vectorstd::list。底层容器的选择会影响 stack 的性能和内存使用方式。

二、C++ Stack 的使用

(一)包含头文件

在使用 stack 之前,需要包含头文件 <stack>

#include <stack>

(二)基本操作示例

#include <iostream>
#include <stack>int main() {// 创建一个空的栈std::stack<int> mystack;// 向栈中压入元素mystack.push(10);mystack.push(20);mystack.push(30);// 访问栈顶元素std::cout << "栈顶元素是: " << mystack.top() << std::endl;// 检查栈是否为空std::cout << "栈是否为空: " << (mystack.empty() ? "是" : "否") << std::endl;// 输出栈的大小std::cout << "栈的大小是: " << mystack.size() << std::endl;// 弹出栈顶元素mystack.pop();// 再次访问栈顶元素std::cout << "弹出一个元素后,栈顶元素是: " << mystack.top() << std::endl;return 0;
}

(三)使用自定义类型

stack 可以存储任何类型的元素,包括自定义类型。自定义类型需要满足底层容器的要求,例如提供默认构造函数、拷贝构造函数等。

#include <iostream>
#include <stack>struct Person {std::string name;int age;Person(const std::string& n, int a) : name(n), age(a) {}
};int main() {std::stack<Person> personStack;personStack.push(Person("Alice", 25));personStack.push(Person("Bob", 30));std::cout << "栈顶元素是: " << personStack.top().name << ", 年龄: " << personStack.top().age << std::endl;return 0;
}

(四)底层容器的选择

可以通过模板参数指定底层容器。例如,使用 std::vector 作为底层容器:

#include <iostream>
#include <stack>
#include <vector>int main() {std::stack<int, std::vector<int>> mystack;mystack.push(10);mystack.push(20);std::cout << "栈顶元素是: " << mystack.top() << std::endl;return 0;
}

不同的底层容器会影响 stack 的性能和内存使用。例如:

  • std::deque(默认):支持快速的插入和删除操作,内存分配相对灵活。
  • std::vector:内存连续,访问速度快,但插入和删除操作可能涉及内存重新分配。
  • std::list:支持双向迭代,插入和删除操作非常高效,但访问速度相对较慢。

一、C++ Queue 介绍

(一)定义

在 C++ 中,queue 是一种容器适配器,它提供了一种先进先出(First In First Out,FIFO)的数据结构。它基于底层容器(默认是 std::deque,也可以是 std::liststd::vector 等)来存储元素,但只允许在容器的一端(队尾)插入元素,在另一端(队首)删除元素。

(二)主要操作

  1. 构造和析构
    • 默认构造函数会创建一个空的队列。
    • 析构函数会销毁队列中的所有元素。
  2. 元素访问
    • front():返回队首元素的引用。如果队列为空,调用 front() 是未定义行为。
    • back():返回队尾元素的引用。如果队列为空,调用 back() 是未定义行为。
  3. 容量
    • empty():检查队列是否为空,如果为空返回 true,否则返回 false
    • size():返回队列中元素的数量。
  4. 修改器
    • push(const T& value):将一个新元素插入到队尾。
    • pop():移除队首元素。注意,pop() 不返回被移除的元素,如果队列为空,调用 pop() 是未定义行为。
    • emplace(Args&&... args):在队尾构造一个新元素,避免了额外的拷贝或移动操作。

(三)底层容器

queue 的底层容器默认是 std::deque,但可以通过模板参数指定其他容器,如 std::liststd::vector。底层容器的选择会影响 queue 的性能和内存使用方式。

二、C++ Queue 的使用

(一)包含头文件

在使用 queue 之前,需要包含头文件 <queue>

#include <queue>

(二)基本操作示例

#include <iostream>
#include <queue>int main() {// 创建一个空的队列std::queue<int> myqueue;// 向队列中插入元素myqueue.push(10);myqueue.push(20);myqueue.push(30);// 访问队首元素std::cout << "队首元素是: " << myqueue.front() << std::endl;// 访问队尾元素std::cout << "队尾元素是: " << myqueue.back() << std::endl;// 检查队列是否为空std::cout << "队列是否为空: " << (myqueue.empty() ? "是" : "否") << std::endl;// 输出队列的大小std::cout << "队列的大小是: " << myqueue.size() << std::endl;// 移除队首元素myqueue.pop();// 再次访问队首元素std::cout << "移除一个元素后,队首元素是: " << myqueue.front() << std::endl;return 0;
}

(三)使用自定义类型

queue 可以存储任何类型的元素,包括自定义类型。自定义类型需要满足底层容器的要求,例如提供默认构造函数、拷贝构造函数等。

#include <iostream>
#include <queue>struct Person {std::string name;int age;Person(const std::string& n, int a) : name(n), age(a) {}
};int main() {std::queue<Person> personQueue;personQueue.push(Person("Alice", 25));personQueue.push(Person("Bob", 30));std::cout << "队首元素是: " << personQueue.front().name << ", 年龄: " << personQueue.front().age << std::endl;return 0;
}

(四)底层容器的选择

可以通过模板参数指定底层容器。例如,使用 std::list 作为底层容器:

#include <iostream>
#include <queue>
#include <list>int main() {std::queue<int, std::list<int>> myqueue;myqueue.push(10);myqueue.push(20);std::cout << "队首元素是: " << myqueue.front() << std::endl;return 0;
}

不同的底层容器会影响 queue 的性能和内存使用。例如:

  • std::deque(默认):支持快速的插入和删除操作,内存分配相对灵活。
  • std::list:支持双向迭代,插入和删除操作非常高效,但访问速度相对较慢。
  • std::vector:内存连续,访问速度快,但插入和删除操作可能涉及内存重新分配。

三、C++ Priority Queue(优先队列)

(一)定义

C++ 标准库还提供了 std::priority_queue,它是一种特殊的队列,元素按照优先级顺序排列。默认情况下,优先级最高的元素(最大值)会被优先处理。

(二)主要操作

  1. 构造和析构
    • 默认构造函数会创建一个空的优先队列。
    • 析构函数会销毁优先队列中的所有元素。
  2. 元素访问
    • top():返回优先级最高的元素的引用。如果优先队列为空,调用 top() 是未定义行为。
  3. 容量
    • empty():检查优先队列是否为空,如果为空返回 true,否则返回 false
    • size():返回优先队列中元素的数量。
  4. 修改器
    • push(const T& value):将一个新元素插入到优先队列中,并根据优先级重新排序。
    • pop():移除优先级最高的元素。注意,pop() 不返回被移除的元素,如果优先队列为空,调用 pop() 是未定义行为。

(三)自定义优先级

可以通过提供自定义比较函数来改变优先级的定义。例如,使用小顶堆(优先级最低的元素优先):

#include <iostream>
#include <queue>
#include <functional> // for std::greaterint main() {// 默认是大顶堆(优先级最高的元素优先)std::priority_queue<int> maxHeap;maxHeap.push(10);maxHeap.push(20);maxHeap.push(30);std::cout << "优先级最高的元素是: " << maxHeap.top() << std::endl;// 自定义小顶堆(优先级最低的元素优先)std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;minHeap.push(10);minHeap.push(20);minHeap.push(30);std::cout << "优先级最低的元素是: " << minHeap.top() << std::endl;return 0;
}

deque简要介绍

std::deque(双端队列,发音为“deck”)是 C++ 标准模板库(STL)中的一种序列容器,它结合了栈和队列的特性,支持在两端(队首和队尾)高效地插入和删除元素。deque 提供了灵活的元素管理方式,同时保持了相对高效的随机访问能力。
在这里插入图片描述

特点

  1. 两端操作高效
    • 在队首和队尾插入或删除元素的时间复杂度为 O(1),这使得 deque 非常适合需要在两端频繁操作的场景。
  2. 支持随机访问
    • 提供了类似数组的随机访问能力,通过下标或迭代器可以快速访问任意位置的元素,时间复杂度为 O(1)
  3. 动态扩展
    • 内部实现为分段连续内存块,可以根据需要动态扩展,支持任意大小的元素存储。
  4. 内存不连续
    • 内部存储是分段的,内存块之间通过指针连接,因此内存不连续。这使得 deque 在某些操作上不如 std::vector 那么高效,但提供了更大的灵活性。

deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,其底层结构如下图所示:
在这里插入图片描述
双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示:
在这里插入图片描述
那deque是如何借助其迭代器维护其假想连续的结构呢?
在这里插入图片描述

deque的缺陷

与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector高的。与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。

但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构。

为什么选择deque作为stack和queue的底层默认容器

stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()pop_back()操作的线性结构,都可以作为stack的底层容器,比如vector和list都可以;

queue是先进先出的特殊线性数据结构,只要具有push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。

但是STL中对stack和queue默认选择deque作为其底层容器,主要是因为:

  1. stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进
    行操作。

  2. 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的
    元素增长时,deque不仅效率高,而且内存使用率高。
    结合了deque的优点,而完美的避开了其缺陷。

相关文章:

  • Postgresql日常使用
  • Redis缓存三大难题:穿透、击穿、雪崩
  • FastDFS分布式储存
  • 【Linux】regmap子系统
  • WEB JWT
  • Java程序员如何设计一个高并发系统?
  • Go 语言安装指南:并解决 `url.JoinPath` 及 `Exec format error` 问题
  • 全栈监控系统架构
  • 大白话解释蓝牙的RPC机制
  • LeetCode 2917.找出数组中的K-or值
  • Linux612 chroot_list开放;FTP服务ftp:get put,指定上传路径报错553;ftp查看文件夹权限
  • Vulkan学习笔记4—图形管线基础
  • ubuntu20.04 安装Mujoco 及 Isaac Gym 仿真器
  • 紫光展锐完成优化升级,支持Android 16,以科技创新共赴智能体验新篇章
  • 常见的测试工具及分类
  • 系统功耗管理
  • 从零搭建智能家居:香橙派+HomeAssistant实战指南
  • 【android bluetooth 框架分析 04】【bt-framework 层详解 6】【Properties介绍】
  • Springboot仿抖音app开发之消息业务模块后端复盘及相关业务知识总结
  • php反序列化漏洞学习
  • 专业做冻货的网站/企业营销型网站建设
  • 一个公司做两个网站可以吗/广告投放网站平台
  • 影视网站模板怎么做/长沙seo优化公司
  • 委托他人建设的网站的侵权责任/湖北最新消息
  • 网站建设案例基本流程图/客户引流推广方案
  • qq在线网站代码生成/网站创建的流程是什么