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)是一种特殊的容器类,它们提供了特定的接口来操作底层容器。容器适配器本身并不直接存储元素,而是通过封装其他容器(如vector
、deque
或list
)来提供特定的功能。C++标准库提供了三种主要的容器适配器:stack
(栈)、queue
(队列)和priority_queue
(优先队列)。
1. stack
(栈)
- 特点:后进先出(LIFO)的数据结构。
- 支持操作:
push
(入栈)、pop
(出栈)、top
(访问栈顶元素)、empty
(判断是否为空)、size
(返回元素个数)。 - 底层容器:默认使用
deque
,也可指定为vector
或list
。可在第二个参数给出。
2. queue
(队列)
- 特点:先进先出(FIFO)的数据结构。
- 支持操作:
push
(入队)、pop
(出队)、front
(访问队首元素)、back
(访问队尾元素)、empty
、size
。 - 底层容器:默认使用
deque
,也可指定为list
。
3. priority_queue
(优先队列)
- 特点:元素按优先级排序,优先级高的元素先出队。
- 支持操作:
push
(插入元素)、pop
(删除优先级最高的元素)、top
(访问优先级最高的元素)、empty
、size
。 - 底层容器:默认使用
vector
,结合heap
算法实现。 - 默认排序:最大堆(大顶堆),即元素按降序排列。
底层容器选择
容器适配器可通过模板参数指定底层容器:
// 使用vector作为stack的底层容器
stack<int, vector<int>> stackWithVector;// 使用list作为queue的底层容器
queue<int, list<int>> queueWithList;
总结
容器适配器 | 数据结构特点 | 默认底层容器 | 适用场景 |
---|---|---|---|
stack | LIFO | deque | 递归模拟、表达式求值 |
queue | FIFO | deque | 任务调度、广度优先搜索 |
priority_queue | 优先级排序 | vector +heap | 任务调度(按优先级)、贪心算法 |
选择合适的容器适配器可以提高代码的可读性和性能。
一、C++ Stack 介绍
(一)定义
在 C++ 中,stack
是一种容器适配器,它提供了一种后进先出(Last In First Out,LIFO)的数据结构。它基于底层容器(默认是 std::deque
,也可以是 std::vector
或 std::list
等)来存储元素,但只允许在容器的一端(称为栈顶)进行操作。
(二)主要操作
- 构造和析构
- 默认构造函数会创建一个空的栈。
- 析构函数会销毁栈中的所有元素。
- 元素访问
top()
:返回栈顶元素的引用。如果栈为空,调用top()
是未定义行为。
- 容量
empty()
:检查栈是否为空,如果为空返回true
,否则返回false
。size()
:返回栈中元素的数量。
- 修改器
push(const T& value)
:将一个新元素压入栈顶。pop()
:移除栈顶元素。注意,pop()
不返回被移除的元素,如果栈为空,调用pop()
是未定义行为。emplace(Args&&... args)
:在栈顶构造一个新元素,避免了额外的拷贝或移动操作。
(三)底层容器
stack
的底层容器默认是 std::deque
,但可以通过模板参数指定其他容器,如 std::vector
或 std::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::list
或 std::vector
等)来存储元素,但只允许在容器的一端(队尾)插入元素,在另一端(队首)删除元素。
(二)主要操作
- 构造和析构
- 默认构造函数会创建一个空的队列。
- 析构函数会销毁队列中的所有元素。
- 元素访问
front()
:返回队首元素的引用。如果队列为空,调用front()
是未定义行为。back()
:返回队尾元素的引用。如果队列为空,调用back()
是未定义行为。
- 容量
empty()
:检查队列是否为空,如果为空返回true
,否则返回false
。size()
:返回队列中元素的数量。
- 修改器
push(const T& value)
:将一个新元素插入到队尾。pop()
:移除队首元素。注意,pop()
不返回被移除的元素,如果队列为空,调用pop()
是未定义行为。emplace(Args&&... args)
:在队尾构造一个新元素,避免了额外的拷贝或移动操作。
(三)底层容器
queue
的底层容器默认是 std::deque
,但可以通过模板参数指定其他容器,如 std::list
或 std::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
,它是一种特殊的队列,元素按照优先级顺序排列。默认情况下,优先级最高的元素(最大值)会被优先处理。
(二)主要操作
- 构造和析构
- 默认构造函数会创建一个空的优先队列。
- 析构函数会销毁优先队列中的所有元素。
- 元素访问
top()
:返回优先级最高的元素的引用。如果优先队列为空,调用top()
是未定义行为。
- 容量
empty()
:检查优先队列是否为空,如果为空返回true
,否则返回false
。size()
:返回优先队列中元素的数量。
- 修改器
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
提供了灵活的元素管理方式,同时保持了相对高效的随机访问能力。
特点
- 两端操作高效
- 在队首和队尾插入或删除元素的时间复杂度为 O(1),这使得
deque
非常适合需要在两端频繁操作的场景。
- 在队首和队尾插入或删除元素的时间复杂度为 O(1),这使得
- 支持随机访问
- 提供了类似数组的随机访问能力,通过下标或迭代器可以快速访问任意位置的元素,时间复杂度为 O(1)。
- 动态扩展
- 内部实现为分段连续内存块,可以根据需要动态扩展,支持任意大小的元素存储。
- 内存不连续
- 内部存储是分段的,内存块之间通过指针连接,因此内存不连续。这使得
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作为其底层容器,主要是因为:
-
stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进
行操作。 -
在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的
元素增长时,deque不仅效率高,而且内存使用率高。
结合了deque的优点,而完美的避开了其缺陷。