C++ 标准库(STL, Standard Template Library)提供了种类丰富的容器,包括序列容器(如 vector
、deque
和 list
)、关联容器(如 map
和 set
)以及适配器容器(如 stack
、queue
和 priority_queue
)。在这些容器中,<stack>
是一种以“后进先出”(LIFO, Last In First Out)为核心特性的适配器容器,经常用于解决递归、括号匹配、路径回溯等问题。
作为一种容器适配器,<stack>
并不直接存储数据,而是基于底层容器(如 deque
或 vector
)提供了一组符合 LIFO 特性的操作接口,这使它在设计层面更加灵活。本文将全面探讨 C++ 标准库中的 stack
,从基础概念、API 用法、内部实现到性能分析,结合实际案例,帮助您深入理解和高效使用这一容器。
第一部分:什么是 <stack>
?
<stack>
是 C++ STL 中的一种容器适配器,它不直接存储元素,而是依赖底层容器(默认是 deque
)实现功能。<stack>
的核心特性是 后进先出(LIFO),即最新添加的元素最先被移除。
1.1 栈的基本概念
栈是一种数据结构,遵循“后进先出”的规则,类似于一叠盘子,最先放入的盘子在最底层,最后放入的盘子在最顶层。栈只允许在一端(称为“栈顶”)进行插入和删除操作。
栈的基本操作包括:
- 压栈(Push):在栈顶插入元素。
- 出栈(Pop):从栈顶移除元素。
- 访问栈顶元素(Top):查看但不移除栈顶的元素。
- 检查栈是否为空(Empty)。
- 获取栈的大小(Size)。
1.2 栈在 C++ 中的位置
在 C++ 中,stack
是一种 容器适配器,定义在头文件 <stack>
中。容器适配器是一种通过修改现有容器(比如 vector
或 deque
)行为而构建的“适配器”,它只暴露特定的接口,并隐藏底层实现的复杂性。
底层容器选择
stack
可以使用以下容器作为其底层实现:
deque
(默认):动态双端队列,支持高效的头尾插入和删除。vector
:动态数组,支持高效的尾部插入,但头部操作效率较低。list
:双向链表,适合频繁插入和删除操作。
开发者可以根据实际需求选择合适的底层容器:
#include <stack>
#include <vector>
#include <deque>
#include <list>
// 使用 vector 作为底层容器
std::stack<int, std::vector<int>> stackUsingVector;
// 使用 list 作为底层容器
std::stack<int, std::list<int>> stackUsingList;
栈的接口
stack
提供了一组有限的接口,专注于栈的核心功能:
函数 | 描述 |
| 在栈顶插入一个新元素 |
| 移除栈顶元素 |
| 返回栈顶元素的引用,但不移除 |
| 检查栈是否为空,返回一个布尔值 |
| 返回栈中元素的数量 |
| 原地构造一个元素并将其压入栈顶(C++11 引入) |
第二部分:快速入门
以下是使用 <stack>
的基本示例代码。
2.1 初始化和基本操作
#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; // 输出: 30
// 出栈操作
myStack.pop(); // 移除栈顶元素
std::cout << "栈顶元素: " << myStack.top() << std::endl; // 输出: 20
// 检查栈是否为空
if (!myStack.empty()) {
std::cout << "栈不为空,大小为: " << myStack.size() << std::endl;
}
return 0;
}
运行结果:
栈顶元素: 30
栈顶元素: 20
栈不为空,大小为: 2
2.2 使用自定义底层容器
虽然 deque
是默认的底层容器,但您也可以选择 vector
或 list
作为底层容器:
#include <iostream>
#include <stack>
#include <vector>
#include <list>
int main() {
// 使用 vector 作为底层容器
std::stack<int, std::vector<int>> stackWithVector;
stackWithVector.push(1);
stackWithVector.push(2);
// 使用 list 作为底层容器
std::stack<int, std::list<int>> stackWithList;
stackWithList.push(3);
stackWithList.push(4);
std::cout << "stackWithVector 栈顶: " << stackWithVector.top() << std::endl;
std::cout << "stackWithList 栈顶: " << stackWithList.top() << std::endl;
return 0;
}
第三部分:高级特性
3.1 原地构造元素(emplace
)
C++11 引入了 emplace()
方法,允许直接在栈顶构造对象,避免了不必要的拷贝或移动操作。
#include <iostream>
#include <stack>
#include <string>
int main() {
std::stack<std::string> myStack;
// 使用 emplace 原地构造字符串
myStack.emplace("Hello");
myStack.emplace("World");
std::cout << "栈顶元素: " << myStack.top() << std::endl; // 输出: World
return 0;
}
3.2 自定义对象作为栈元素
stack
可以存储任何类型的对象,包括自定义的类对象。以下是一个示例:
#include <iostream>
#include <stack>
struct Point {
int x, y;
Point(int x, int y) : x(x), y(y) {}
};
int main() {
std::stack<Point> pointStack;
// 压栈
pointStack.emplace(1, 2);
pointStack.emplace(3, 4);
// 访问栈顶元素
Point topPoint = pointStack.top();
std::cout << "栈顶点: (" << topPoint.x << ", " << topPoint.y << ")" << std::endl;
return 0;
}
运行结果:
栈顶点: (3, 4)
3.3 与其他容器的转换
尽管 stack
是一个容器适配器,但您可以通过底层容器的特性来实现与其他容器的转换。例如,将 stack
数据导出到 vector
:
#include <iostream>
#include <stack>
#include <vector>
int main() {
std::stack<int> myStack;
// 压栈
myStack.push(10);
myStack.push(20);
myStack.push(30);
// 导出到 vector
std::vector<int> myVector;
while (!myStack.empty()) {
myVector.push_back(myStack.top());
myStack.pop();
}
// 输出 vector 中的数据
for (int elem : myVector) {
std::cout << elem << " ";
}
return 0;
}
运行结果:
30 20 10
第四部分:性能分析
4.1 时间复杂度
操作 | 时间复杂度 |
| O(1) |
| O(1) |
| O(1) |
| O(1) |
| O(1) |
stack
的所有操作均具有常数时间复杂度,这使得它非常适合高效的栈操作。
4.2 底层容器对性能的影响
deque
(默认):
提供高效的头尾插入和删除操作,是实现stack
的推荐选择。vector
:
尾部插入性能高,但在动态扩容时会有一定的额外开销。list
:
插入和删除性能高,但由于链表节点的额外指针存储开销,内存使用效率较低。
第五部分:实际案例
5.1 括号匹配
stack
是解决括号匹配问题的经典工具:
#include <iostream>
#include <stack>
bool isValid(const std::string& str) {
std::stack<char> s;
for (char ch : str) {
if (ch == '(' || ch == '{' || ch == '[') {
s.push(ch);
} else {
if (s.empty()) return false;
char top = s.top();
if ((ch == ')' && top == '(') ||
(ch == '}' && top == '{') ||
(ch == ']' && top == '[')) {
s.pop();
} else {
return false;
}
}
}
return s.empty();
}
int main() {
std::string input = "{[()]}";
std::cout << (isValid(input) ? "合法" : "不合法") << std::endl;
return 0;
}
运行结果:
合法
5.2 深度优先搜索(DFS)
stack
常用于实现图的深度优先搜索(DFS)算法。
第六部分:注意事项
6.1 不支持遍历
stack
没有提供迭代器接口,无法直接进行遍历。如果需要遍历栈中的元素,可以使用底层容器或临时变量存储。
6.2 选择合适的底层容器
根据性能需求选择合适的底层容器。例如,deque
是默认的选择,但如果操作过程非常简单且以较少的内存占用为目标,可以选择 vector
。
总结
C++ 标准库中的 <stack>
是一种功能强大且易于使用的容器适配器,通过它,开发者可以快速实现各种与“后进先出”相关的算法和数据结构。在实际使用中,了解底层容器的特性和选择非常重要,可以为项目的性能和内存效率带来显著提升。