stack
使用指南#include
using namespace std;//或者std::stack st;
接口 | 说明 |
---|---|
stack |
构造空栈 |
push(x) |
将元素 x 压入栈顶 |
pop() |
弹出栈顶元素(不返回值) |
top() |
返回栈顶元素引用 |
empty() |
检测栈是否为空(返回布尔值) |
size() |
返回栈中元素个数 |
queue
使用指南#include
using namespace std;
接口 | 说明 |
---|---|
queue |
构造空队列 |
push(x) |
将元素 x 入队(队尾) |
pop() |
弹出队头元素(不返回值) |
front() |
返回队头元素引用 |
back() |
返回队尾元素引用 |
empty() |
检测队列是否为空 |
size() |
返回队列中元素个数 |
class MyStack {
private:
queue q1, q2; // 使用两个队列模拟栈
public:
void push(int x) {
q1.push(x); // 新元素始终加入非空队列
}
int pop() {
if (q1.empty()) swap(q1, q2); // 切换队列
while (q1.size() > 1) {
q2.push(q1.front()); // 转移元素,保留最后一个(栈顶)
q1.pop();
}
int val = q1.front();
q1.pop();
return val;
}
int top() { return q1.empty() ? q2.back() : q1.back(); }
};
priority_queue
使用指南#include
using namespace std;
接口 | 说明 |
---|---|
priority_queue |
构造大顶堆(默认) |
push(x) |
插入元素 x 并调整堆结构 |
pop() |
弹出堆顶元素(最大 / 最小值) |
top() |
返回堆顶元素引用 |
empty() |
检测优先队列是否为空 |
size() |
返回元素个数 |
stack
和 queue
本质是容器适配器,通过封装其他容器(如 deque
、vector
)的接口实现特定功能。deque
作为默认底层容器?deque
的优势:
list
,连续存储(分段连续)减少指针开销。vector
的缺陷:扩容时无需搬移大量元素,适合频繁插入 / 删除场景。stack
仅需尾插 / 尾删,queue
需尾插 / 头删,deque
完美满足需求。deque 是一种 双开口的 “连续” 空间数据结构,但实际并非物理连续,而是由 一段段连续的小空间(缓冲区)拼接而成,整体呈现 “逻辑连续” 的假象 。
每个缓冲区(buffer)通常是固定大小的数组(如 8 或 16 字节)。
缓冲区之间通过 中控器(map) 管理,中控器是一个指针数组,每个元素指向一个缓冲区的起始地址 。
vector
的随机访问能力,又支持高效的双端操作。deque 的迭代器需要维护 “逻辑连续” 的假象,因此设计复杂。每个迭代器包含以下信息 :
cur
:当前指针,指向缓冲区中的具体元素。
first
:当前缓冲区的起始地址。
last
:当前缓冲区的结束地址(不包含)。
node
:指向中控器中当前缓冲区对应的指针(即 map 中的元素)。
迭代器移动逻辑:
cur
指向当前缓冲区的末尾时,迭代器通过 node
切换到下一个缓冲区,并将 cur
指向新缓冲区的起始位置,反之亦然。vector
一样进行 ++
/--
操作,但实际会涉及缓冲区的切换 。vector
一样搬移大量元素,只需在头部缓冲区添加 / 删除元素,或新建 / 销毁一个缓冲区(极端情况) 。vector
类似,直接操作尾部缓冲区,仅在缓冲区满时新建一个缓冲区 。特性 | deque | vector | list |
---|---|---|---|
内存结构 | 分段连续(逻辑连续) | 物理连续 | 非连续(链表) |
头插 / 头删效率 | O (1)(无需搬移元素) | O (n)(需搬移全部元素) | O(1) |
随机访问效率 | O (1)(通过迭代器切换缓冲区) | O (1)(直接指针运算) | O (n)(需遍历链表) |
空间利用率 | 高(缓冲区紧凑,无额外指针) | 高(连续存储) | 低(每个节点含前驱 / 后继指针) |
典型场景 | 双端操作频繁(如 stack/queue 底层) | 随机访问频繁(如数组下标操作) | 任意位置插入 / 删除频繁 |
vector
的原生指针遍历。因此,deque 不适合高频遍历场景 。push_back
/pop_back
)、队列(push_back
/pop_front
)。begin()
到 end()
的遍历(如 for (auto it = d.begin(); it != d.end(); ++it)
)。push_back
/pop_back
,deque 的尾操作与 vector
等价,但扩容时无需搬移元素(效率更高) 。push_back
/pop_front
,deque 的头删操作(pop_front
)效率优于 vector
(vector
头删需搬移所有元素),且空间利用率优于 list
。vector
或 list
。知识点 | 知识点 |
deque 的逻辑结构 | 分段连续空间,通过中控器管理缓冲区,呈现 “逻辑连续” 假象 |
迭代器设计 | 头插 / 头删效率优于 vector,空间利用率优于 list |
双端操作优势 | 迭代器需频繁检测缓冲区边界,遍历效率低,不适合高频遍历场景 |
遍历缺陷 | 迭代器需频繁检测缓冲区边界,遍历效率低,不适合高频遍历场景 |
作为 stack/queue 底层 | 双端操作高效 无需遍历,完美适配 stack/queue 的接口需求 |
总结:deque 的核心价值
deque 的设计是 双端操作高效性 与 空间利用率 的平衡:
algorithm
算法优先推荐使用 vector
或 list
。operator()
的类或结构体。优先队列默认使用 大顶堆(通过 <
运算符比较元素),若需创建 小顶堆,需显式指定仿函数 greater
。例如:
#include
#include // 包含 greater 仿函数
void TestPriorityQueue() {
vector v = {3, 1, 4, 2};
// 默认大顶堆(使用 less 仿函数,可省略不写)
priority_queue max_heap;
for (int x : v) max_heap.push(x); // 堆顶为 4
// 显式使用 greater 仿函数创建小顶堆
priority_queue, greater> min_heap(v.begin(), v.end());
// 堆顶为 1(最小值)
}
当优先队列存储自定义类型时,需通过仿函数或重载运算符定义比较规则。以下是通过独立仿函数实现相同功能的方式:
class Date {
public:
int _year, _month, _day;
Date(int y, int m, int d) : _year(y), _month(m), _day(d) {}
};
// 自定义仿函数:大顶堆(按日期从大到小排序)
struct DateGreater {
bool operator()(const Date& d1, const Date& d2) const {
if (d1._year != d2._year)
return d1._year > d2._year;
if (d1._month != d2._month)
return d1._month > d2._month;
return d1._day > d2._day;
}
};
// 自定义仿函数:小顶堆(按日期从小到大排序)
struct DateLess {
bool operator()(const Date& d1, const Date& d2) const {
if (d1._year != d2._year)
return d1._year < d2._year;
if (d1._month != d2._month)
return d1._month < d2._month;
return d1._day < d2._day;
}
};
// 使用示例
void TestCustomFunctor() {
vector dates = {
Date(2023, 10, 5),
Date(2023, 10, 3),
Date(2023, 11, 1)
};
// 大顶堆:优先取出最新日期
priority_queue, DateGreater> max_heap(dates.begin(), dates.end());
cout << max_heap.top()._year << "-" << max_heap.top()._month << "-" << max_heap.top()._day << endl; // 输出:2023-11-1
// 小顶堆:优先取出最旧日期
priority_queue, DateLess> min_heap(dates.begin(), dates.end());
cout << min_heap.top()._year << "-" << min_heap.top()._month << "-" << min_heap.top()._day << endl; // 输出:2023-10-3
}
Date
类的日期顺序),建议重载 operator<
(对应大顶堆)或 operator>
(对应小顶堆),保持代码简洁。less
仿函数实现。greater
或自定义仿函数,如: priority_queue, greater> min_heap; // 小顶堆
bool
,且遵循 严格弱序关系(strict weak ordering),确保堆结构正确维护。灵活控制排序规则:通过仿函数,优先队列可轻松切换大顶堆 / 小顶堆,或为自定义类型定制比较逻辑。
deque
)namespace My_stack {
template>
class stack { /* ... */ };
}
T
:栈中存储元素的类型(必填)。Con
:底层容器类型(可选,默认 std::deque
),文档中提到 stack
本质是容器适配器,可封装任意支持 push_back
/pop_back
/back
接口的容器(如 vector
、list
) 。My_stack
避免与标准库 std::stack
命名冲突,符合自定义组件的封装规范。stack() {}
_c
调用 Con
的默认构造函数(如 deque
的空构造)。push(const T& x)
void push(const T& x) { _c.push_back(x); }
push_back
接口,在容器尾部添加元素,符合栈的 “后进先出” 特性 。Con
为 deque
,元素被添加到双端队列的尾部,时间复杂度 O (1)(均摊)。pop()
void pop() { _c.pop_back(); }
pop_back
接口,移除尾部元素(即栈顶元素),不返回值 。pop()
,会导致未定义行为(需提前通过 empty()
检测)。top()
T& top() { return _c.back(); }
const T& top()const { return _c.back(); }
const
版本:返回栈顶元素的可修改引用。const
版本:返回栈顶元素的常量引用,用于 const stack
对象。back()
接口获取尾部元素,时间复杂度 O (1) 。size()
和 empty()
size_t size()const { return _c.size(); }
bool empty()const { return _c.empty(); }
功能:
size()
:返回栈中元素个数,调用容器的 size()
接口。empty()
:检测栈是否为空,调用容器的 empty()
接口,等价于 size() == 0
。deque
的原因class stack { template> /* ... */ }
stack
的默认底层容器为 deque
,因其双端操作高效且内存利用率高。
deque
的 push_back
/pop_back
均为 O (1) 操作,且扩容时无需像 vector
一样搬移大量元素 。deque
遍历效率低的缺陷 。Con
为 vector
或 list
,需确保容器支持以下接口: push_back(const T&) // 压栈
pop_back() // 弹栈
back() const& // 获取栈顶
size() const // 元素个数
empty() const // 判空
#include
using namespace std;
int main() {
My_stack::stack s; // 使用默认 deque 作为底层容器
s.push(10);
s.push(20);
cout << "栈顶元素:" << s.top() << endl; // 输出 20
s.pop();
cout << "栈大小:" << s.size() << endl; // 输出 1
cout << "是否为空:" << boolalpha << s.empty() << endl; // 输出 false
return 0;
}
deque
)namespace My_Queue
{
template>
class queue
{
public:
queue()
{ }
void push(const T& x)
{
_c.push_back(x);
}
void pop()
{
_c.pop_front();
}
T& back()
{
return _c.back();
}
const T& back()const
{
return _c.back();
}
T& front()
{
return _c.front();
}
const T& front()const
{
return _c.front();
}
size_t size()const
{
return _c.size();
}
bool empty()const
{
return _c.empty();
}
private:
Con _c;
};
};
vector
和堆算法)template, class compare=less>
class priority_queue { /* ... */ };
T
:优先队列中存储元素的类型(必填)。container
:底层容器类型(可选,默认 std::vector
),需支持随机访问(如 vector
、deque
),文档提到优先队列底层容器需满足 push_back
/pop_back
/front
等接口 。compare
:比较仿函数(可选,默认 less
),用于定义元素优先级。less
表示大顶堆(默认),greater
表示小顶堆 。priority_queue()
priority_queue() { }
_con
调用 container
的默认构造函数(如 vector
的空构造)。push(const T& val)
void push(const T& val) {
_con.push_back(val); // 尾部添加元素
Adjustup(_con.size()-1); // 向上调整堆,维持堆性质
}
Adjustup
从下往上调整堆,确保父节点优先级高于子节点(大顶堆场景)。Adjustup
算法:
child
)与父节点(parent
),若父节点优先级低于子节点(com(_con[parent], _con[child])
为真),则交换两者位置,直至堆性质满足 。void Adjustup(size_t child)
{
compare com;
size_t parent = (child - 1) / 2;
while (child > 0)
{
if (com(_con[parent], _con[child]))
{
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
pop()
void pop() {
swap(_con[0], _con[size() - 1]); // 交换堆顶与最后一个元素
_con.pop_back(); // 删除最后一个元素(原堆顶)
Adjustdown(0); // 向下调整堆,维持堆性质
}
Adjustdown
从上往下调整堆,重新确定新堆顶的位置。Adjustdown
算法:
parent
)与左右子节点(child
),选择优先级最高的子节点交换位置,直至堆性质满足 。void Adjustdown(size_t parent)
{
compare com;
size_t child = 2 * parent + 1;
size_t size = size();
while (child < size)
{
if (child + 1 < size && com(_con[child] ,_con[child + 1]))
{
child++;
}
if (com(_con[parent] , _con[child]))
{
swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
top()
const T& top() { return _con[0]; }
_con[0]
会导致未定义行为(需提前通过 empty()
检测)。size()
和 empty()
size_t size() { return _con.size(); }
bool empty() { return _con.empty(); }
size()
:返回队列中元素个数,调用容器的 size()
接口。empty()
:检测队列是否为空,等价于 size() == 0
。container
vector
的原因:
vector
支持随机访问(通过下标 []
),满足堆算法(Adjustup
/Adjustdown
)的索引需求。push_back
/pop_back
操作高效,且扩容时通过复制实现,保证数据连续性 。deque
,需确保其支持随机访问(deque
符合要求),但实际优先队列更常用 vector
作为底层容器(文档默认场景) 。compare
less
(大顶堆): compare com; // 实例化仿函数
if (com(_con[parent], _con[child])) { // 若 parent < child(大顶堆逻辑)
swap(_con[child], _con[parent]); // 父节点与子节点交换,使父节点更大
}
greater
):compare
改为 greater
,此时 com(a, b)
判断 a > b
是否成立,调整堆时确保父节点更小。Date
类的比较逻辑,可自定义仿函数实现特定优先级规则,如: struct MyCompare {
bool operator()(const int& a, const int& b) {
return a % 10 < b % 10; // 按个位数大小排序
}
};
priority_queue, MyCompare> pq; // 使用自定义比较规则
#include
using namespace std;
int main() {
priority_queue pq; // 默认大顶堆,底层 vector,比较仿函数 less
pq.push(30);
pq.push(10);
pq.push(20);
cout << "堆顶元素(最大值):" << pq.top() << endl; // 输出 30
pq.pop();
cout << "新堆顶元素:" << pq.top() << endl; // 输出 20
return 0;
}
class MinStack {
private:
stack data; // 存储所有元素
stack min_data; // 存储最小值
public:
void push(int x) {
data.push(x);
if (min_data.empty() || x <= min_data.top()) {
min_data.push(x);
}
}
void pop() {
if (data.top() == min_data.top()) {
min_data.pop();
}
data.pop();
}
int top() { return data.top(); }
int getMin() { return min_data.top(); }
};
题目:给定入栈序列 pushV
和出栈序列 popV
,判断 popV
是否为 pushV
的有效弹出序列。
思路:用栈模拟入栈和出栈过程,逐个匹配出栈元素。
代码:
bool IsPopOrder(vector pushV, vector popV) {
if (pushV.size() != popV.size()) return false;
stack s;
int i = 0; // 入栈指针
for (int num : popV) {
// 栈顶元素不等于当前出栈元素时,继续入栈
while (s.empty() || s.top() != num) {
if (i >= pushV.size()) return false; // 无元素可入栈
s.push(pushV[i++]);
}
s.pop(); // 匹配成功,弹出栈顶
}
return true;
}
题目:根据逆波兰表达式(后缀表达式)计算结果,运算符包括 +
、-
、*
、/
。
思路:用栈存储操作数,遇到运算符时弹出栈顶两个元素计算结果并压栈。
代码:
int evalRPN(vector& tokens) {
stack sv;
for(const auto& e:tokens)
{
if(e=="+"||e=="-"||e=="*"||e=="/")
{
int a=sv.top();
sv.pop();
int b=sv.top();
sv.pop();
switch(e[0])
{
case '/':
sv.push(b/a);
break;
case '*':
sv.push(a*b);
break;
case '-':
sv.push(b-a);
break;
case '+':
sv.push(a+b);
break;
}
}
else
{
sv.push(stoi(e));
}
}
return sv.top();
}
数据结构 | 访问规则 | 底层容器(STL 默认) | 典型场景 |
---|---|---|---|
stack |
后进先出(LIFO) | deque |
函数调用、表达式求值 |
queue |
先进先出(FIFO) | deque |
BFS、任务队列 |
priority_queue |
优先级优先 | vector (堆结构) |
堆排序、最短路径算法 |
deque
的分段连续存储结构与迭代器设计。priority_queue
中使用自定义类时,需重载比较运算符(<
或 >
)。通过本文的理论解析与实战代码,相信你已掌握 C++ 中栈与队列的核心概念与应用。在实际开发中,根据场景选择合适的数据结构,能显著提升代码效率与可读性。
//stack.h
#include
#include
namespace My_stack
{
template>
class stack
{
public:
stack()
{
}
void push(const T& x)
{
_c.push_back(x);
}
void pop()
{
_c.pop_back();
}
T& top()
{
return _c.back();
}
const T& top()const
{
return _c.back();
}
size_t size()const
{
return _c.size();
}
bool empty()const
{
return _c.empty();
}
private:
Con _c;
};
};
//queue.h
namespace My_Queue
{
template>
class queue
{
public:
queue()
{ }
void push(const T& x)
{
_c.push_back(x);
}
void pop()
{
_c.pop_front();
}
T& back()
{
return _c.back();
}
const T& back()const
{
return _c.back();
}
T& front()
{
return _c.front();
}
const T& front()const
{
return _c.front();
}
size_t size()const
{
return _c.size();
}
bool empty()const
{
return _c.empty();
}
private:
Con _c;
};
template
class less
{
public:
bool operator()(const T& a, const T& b)
{
return a < b;
}
};
template
class greater
{
public:
bool operator()(const T& a, const T& b)
{
return a > b;
}
};
template,class compare=less>
class priority_queue
{
public:
priority_queue()
{ }
void push(const T& val)
{
_con.push_back(val);
Adjustup(_con.size()-1);
}
void pop()
{
swap(_con[0], _con[size() - 1]);
_con.push_back();
Adjustdown(0);
}
const T& top()
{
return _con[0];
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
void Adjustup(size_t child)
{
compare com;
size_t parent = (child - 1) / 2;
while (child > 0)
{
if (com(_con[parent], _con[child]))
{
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void Adjustdown(size_t parent)
{
compare com;
size_t child = 2 * parent + 1;
size_t size = size();
while (child < size)
{
if (child + 1 < size && com(_con[child] ,_con[child + 1]))
{
child++;
}
if (com(_con[parent] , _con[child]))
{
swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
private:
container _con;
};
};