📖 1.1 什么是栈
栈是一种线性数据结构,具有后进先出(Last-In-First-Out,LIFO)的特点。可以类比为装满盘子的餐桌,每次放盘子都放在最上面,取盘子时也从最上面取,因此最后放进去的盘子最先取出。栈的典型应用场景有函数调用、括号匹配、表达式求值等。
元素从固定一侧开口进入和离开栈的操作,分别称为入栈和出栈。
栈中元素由深到浅存储,我们将栈最深的存储位置称为栈底,当前栈中最外围元素所在的位置则称为栈顶。
栈底通常固定不变,而栈顶则由当前栈中最外侧元素位置(栈中元素数量)确定。
由栈的特点可知栈的插入和删除(入栈和出栈)操作都是在栈顶这一端进行的。
栈顶(Top):线性表允许进行插入删除的那一端。
栈底(Bottom):固定的,不允许进行插入和删除的另一端。
空栈:不含任何元素的空表。
栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构
栈的常见基本操作
InitStack(&S):初始化一个空栈S 。
StackEmpty(S):判断一个栈是否为空,若栈为空则返回true,否则返回false。
Push(&S, x):进栈(栈的插入操作),若栈S未满,则将x加入使之成为新栈顶。
Pop(&S, &x):出栈(栈的删除操作),若栈S非空,则弹出栈顶元素,并用x返回。
GetTop(S, &x):读栈顶元素,若栈S非空,则用x返回栈顶元素。
DestroyStack(&S):栈销毁,并释放S占用的存储空间(“&”表示引用调用)。
💻 1.2 栈的实现(数组模拟)
栈的实现可以使用数组来模拟。我们可以定义一个固定大小的数组和一个指向栈顶的指针。栈顶指针初始化为 -1,表示栈为空。每次入栈操作时,将元素放入数组并将栈顶指针加一;出栈操作时,将栈顶指针减一。
#include <iostream>
using namespace std;
const int MAX_SIZE = 100; // 栈的最大容量
int stack[MAX_SIZE]; // 栈的数组
int top = -1; // 栈顶指针
// 入栈操作
void push(int x) {
if (top >= MAX_SIZE - 1) {
cout << "栈已满,无法入栈!" << endl;
return;
}
stack[++top] = x;
}
// 出栈操作
void pop() {
if (top == -1) {
cout << "栈为空,无法出栈!" << endl;
return;
}
top--;
}
// 获取栈顶元素
int peek() {
if (top == -1) {
cout << "栈为空,无栈顶元素!" << endl;
return -1;
}
return stack[top];
}
// 判断栈是否为空
bool isEmpty() {
return top == -1;
}
// 获取栈的大小
int size() {
return top + 1;
}
int main() {
push(1);
push(2);
push(3);
cout << "当前栈顶元素:" << peek() << endl;
cout << "栈的大小:" << size() << endl;
pop();
cout << "当前栈顶元素:" << peek() << endl;
cout << "栈是否为空:" << (isEmpty() ? "是" : "否") << endl;
return 0;
}
运行结果:
当前栈顶元素:3
栈的大小:3
当前栈顶元素:2
栈是否为空:否
📚 1.3 栈的实现(STL 方法)
在 C++ 中,我们也可以使用标准模板库(STL)提供的 stack
容器来实现栈。使用 stack
容器非常方便,不需要手动处理数组和指针。
#include <iostream>
#include <stack>
using namespace std;
int main() {
stack<int> st;
st.push(1);
st.push(2);
st.push(3);
cout << "当前栈顶元素:" << st.top() << endl;
cout << "栈的大小:" << st.size() << endl;
st.pop();
cout << "当前栈顶元素:" << st.top() << endl;
cout << "栈是否为空:" << (st.empty() ? "是" : "否") << endl;
return 0;
}
运行结果:
当前栈顶元素:3
栈的大小:3
当前栈顶元素:2
栈是否为空:否
使用 stack
容器,我们可以更方便地实现栈的入栈、出栈、获取栈顶元素等操作,而无需自己处理底层的数据结构。
🚂 1.4 出入栈顺序(火车调度案例)
火车调度是一个经典的栈的应用场景。给定 n 辆火车的进站顺序,请输出所有可能的出站顺序。
我们使用递归来生成所有可能的出站顺序。对于每一辆火车,它可以先进站再出站,也可以直接从进站过程中出站。我们不断尝试所有可能的出站顺序,直到所有火车都出站。
#include <iostream>
#include <vector>
using namespace std;
void dfs(vector<int>& in, vector<int>& out, vector<int>& path) {
if (in.empty() && out.empty()) {
for (int num : path) {
cout << num << " ";
}
cout << endl;
return;
}
if (!in.empty()) {
int num = in.back();
in.pop_back();
path.push_back(num);
dfs(in, out, path);
path.pop_back();
in.push_back(num);
}
if (!out.empty()) {
int num = out.back();
out.pop_back();
path.push_back(num);
dfs(in, out, path);
path.pop_back();
out.push_back(num);
}
}
int main() {
vector<int> in = {1, 2, 3};
vector<int> out;
vector<int> path;
dfs(in, out, path);
return 0;
}
运行结果:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
🧩 1.5 栈的应用
有效的括号
题目描述
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
提示:
1 <= s.length <= 104
s
仅由括号'()[]{}'
组成
力扣官方题解https://leetcode.cn/problems/valid-parentheses/solutions/373578/you-xiao-de-gua-hao-by-leetcode-solution/
我们遍历给定的字符串 s。当我们遇到一个左括号时,我们会期望在后续的遍历中,有一个相同类型的右括号将其闭合。由于后遇到的左括号要先闭合,因此我们可以将这个左括号放入栈顶。
当我们遇到一个右括号时,我们需要将一个相同类型的左括号闭合。此时,我们可以取出栈顶的左括号并判断它们是否是相同类型的括号。如果不是相同的类型,或者栈中并没有左括号,那么字符串 s 无效,返回False。为了快速判断括号的类型,我们可以使用哈希表存储每一种括号。哈希表的键为右括号,值为相同类型的左括号。
在遍历结束后,如果栈中没有左括号,说明我们将字符串 s 中的所有左括号闭合,返回 True,否则返回 False。
注意到有效字符串的长度一定为偶数,因此如果字符串的长度为奇数,我们可以直接返回 False,省去后续的遍历判断过程。
栈在括号匹配中有着重要的应用。给定一个只包含 (
、)
、[
、]
、{
和 }
的字符串,判断括号是否匹配。
使用栈可以轻松解决这个问题。遍历字符串中的每个字符,如果是左括号,将其入栈;如果是右括号,判断与栈顶元素是否匹配,若匹配则出
栈,否则返回 false。最后检查栈是否为空,为空则括号匹配。
#include <iostream>
#include <stack>
#include <unordered_map>
using namespace std;
bool isValid(string s) {
stack<char> st;
unordered_map<char, char> mapping = {{')', '('}, {']', '['}, {'}', '{'}};
for (char c : s) {
if (c == '(' || c == '[' || c == '{') {
st.push(c);
} else if (c == ')' || c == ']' || c == '}') {
if (st.empty() || st.top() != mapping[c]) {
return false;
}
st.pop();
}
}
return st.empty();
}
int main() {
cout << isValid("()") << endl; // 输出:1 (true)
cout << isValid("()[]{}") << endl; // 输出:1 (true)
cout << isValid("(]") << endl; // 输出:0 (false)
return 0;
}
📝 总结
栈是一种非常重要的数据结构,在计算机算法中有着广泛的应用。通过模拟入栈和出栈操作,我们可以解决很多实际问题,比如火车调度、括号匹配等。同时,C++ 中也提供了标准模板库(STL)中的 stack
容器,方便我们使用栈来解决问题。熟练掌握栈的使用将会对你的编程技能有很大的提升。
⭐️希望本篇文章对你有所帮助。
⭐️如果你有任何问题或疑惑,请随时向提问。
⭐️感谢阅读!