一、什么是栈?
在学习数据结构时,栈 是一种非常重要、也非常基础的线性结构。
栈的英文是 Stack,它是一种特殊的线性表。和普通线性表不同的是,栈只允许在一端进行插入和删除操作。
这个允许插入和删除的一端,称为 栈顶 ;另一端称为 栈底。
栈最大的特点是:
后进先出
也就是:
Last In First Out,简称 LIFO
举一个生活中的例子:
我们把一摞盘子一个一个叠起来,最先放进去的盘子在最下面,最后放上去的盘子在最上面。当我们取盘子时,通常也是先取最上面的那个。
这就是一个典型的栈结构。
二、栈的基本特点
栈是一种受限制的线性表,它具有以下特点:
-
只能在栈顶进行插入和删除操作。
-
插入操作称为 入栈 或 压栈。
-
删除操作称为 出栈。
-
最后入栈的元素,会最先出栈。
-
最先入栈的元素,会最后出栈。
例如依次将元素:
1 2 3
入栈,那么栈中的结构可以理解为:
栈顶 -> 3
2
栈底 -> 1
此时如果进行出栈操作,最先被删除的是元素 3,然后是 2,最后才是 1。
三、栈的基本操作
栈常见的基本操作有以下几种:
| 操作名称 | 英文名称 | 含义 |
|---|---|---|
| 入栈 | push | 向栈顶插入一个元素 |
| 出栈 | pop | 删除栈顶元素 |
| 取栈顶元素 | top / peek | 获取当前栈顶元素 |
| 判断栈是否为空 | empty | 判断栈中是否没有元素 |
| 获取栈中元素个数 | size | 返回栈中元素数量 |
下面分别进行介绍。
四、入栈操作 push
入栈就是将一个新元素放到栈顶。
假设原来的栈是:
栈顶 -> 2
栈底 -> 1
现在执行:
push(3);
入栈之后变成:
栈顶 -> 3
2
栈底 -> 1
可以看到,新元素 3 被放到了栈顶。
在 C++ STL 中,入栈操作使用:
st.push(x);
示例代码:
#include <iostream>
#include <stack>
using namespace std;
int main() {
stack<int> st;
st.push(1);
st.push(2);
st.push(3);
cout << "元素入栈完成" << endl;
return 0;
}
五、出栈操作 pop
出栈就是删除当前栈顶元素。
假设当前栈为:
栈顶 -> 3
2
栈底 -> 1
执行一次出栈操作:
pop();
出栈之后变成:
栈顶 -> 2
栈底 -> 1
原来的栈顶元素 3 被删除。
在 C++ STL 中,出栈操作使用:
st.pop();
需要注意的是:
pop() 只负责删除栈顶元素,不会返回被删除的元素。
如果想要获取栈顶元素,需要先使用 top(),然后再使用 pop()。
示例代码:
#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;
st.pop();
cout << "出栈后,新的栈顶元素是:" << st.top() << endl;
return 0;
}
运行结果:
当前栈顶元素是:3
出栈后,新的栈顶元素是:2
六、获取栈顶元素 top
栈顶元素就是最后一个入栈、并且还没有被删除的元素。
在 C++ STL 中,可以使用:
st.top();
来获取栈顶元素。
示例代码:
#include <iostream>
#include <stack>
using namespace std;
int main() {
stack<int> st;
st.push(10);
st.push(20);
st.push(30);
cout << "栈顶元素是:" << st.top() << endl;
return 0;
}
运行结果:
栈顶元素是:30
需要注意的是:
在使用 top() 之前,最好先判断栈是否为空。
如果栈为空,直接调用 top() 会导致程序出现错误。
七、判断栈是否为空 empty
判断栈是否为空,可以使用:
st.empty();
如果栈为空,返回 true;否则返回 false。
示例代码:
#include <iostream>
#include <stack>
using namespace std;
int main() {
stack<int> st;
if (st.empty()) {
cout << "当前栈为空" << endl;
} else {
cout << "当前栈不为空" << endl;
}
st.push(100);
if (st.empty()) {
cout << "当前栈为空" << endl;
} else {
cout << "当前栈不为空" << endl;
}
return 0;
}
运行结果:
当前栈为空
当前栈不为空
八、获取栈的元素个数 size
获取栈中元素的数量,可以使用:
st.size();
示例代码:
#include <iostream>
#include <stack>
using namespace std;
int main() {
stack<int> st;
st.push(1);
st.push(2);
st.push(3);
cout << "当前栈中元素个数为:" << st.size() << endl;
return 0;
}
运行结果:
当前栈中元素个数为:3
九、完整示例:栈的基本操作演示
下面通过一个完整程序,演示栈的入栈、出栈、获取栈顶元素、判断是否为空等操作。
#include <iostream>
#include <stack>
using namespace std;
int main() {
stack<int> st;
// 入栈操作
st.push(10);
st.push(20);
st.push(30);
cout << "当前栈中元素个数:" << st.size() << endl;
cout << "当前栈顶元素:" << st.top() << endl;
// 出栈操作
st.pop();
cout << "执行一次出栈后:" << endl;
cout << "当前栈中元素个数:" << st.size() << endl;
cout << "当前栈顶元素:" << st.top() << endl;
// 继续出栈
st.pop();
st.pop();
if (st.empty()) {
cout << "栈已经为空" << endl;
} else {
cout << "栈不为空" << endl;
}
return 0;
}
运行结果:
当前栈中元素个数:3
当前栈顶元素:30
执行一次出栈后:
当前栈中元素个数:2
当前栈顶元素:20
栈已经为空
十、用数组模拟栈
除了使用 C++ STL 中的 stack,我们也可以使用数组手动模拟栈。
数组模拟栈的核心思想是:
使用一个变量 top 表示栈顶位置。
初始时:
top = -1;
表示栈为空。
每次入栈时,先让 top 加一,然后把元素放到 top 位置。
每次出栈时,只需要让 top 减一即可。
示例代码:
#include <iostream>
using namespace std;
const int MAXN = 100;
int st[MAXN];
int topIndex = -1;
// 入栈
void push(int x) {
if (topIndex == MAXN - 1) {
cout << "栈已满,无法入栈" << endl;
return;
}
st[++topIndex] = x;
}
// 出栈
void pop() {
if (topIndex == -1) {
cout << "栈为空,无法出栈" << endl;
return;
}
topIndex--;
}
// 获取栈顶元素
int top() {
if (topIndex == -1) {
cout << "栈为空,没有栈顶元素" << endl;
return -1;
}
return st[topIndex];
}
// 判断栈是否为空
bool empty() {
return topIndex == -1;
}
// 获取栈中元素个数
int size() {
return topIndex + 1;
}
int main() {
push(10);
push(20);
push(30);
cout << "栈顶元素:" << top() << endl;
cout << "栈中元素个数:" << size() << endl;
pop();
cout << "出栈后栈顶元素:" << top() << endl;
return 0;
}
运行结果:
栈顶元素:30
栈中元素个数:3
出栈后栈顶元素:20
十一、栈的常见应用
栈虽然结构简单,但是应用非常广泛。
常见应用包括:
1. 函数调用
程序在调用函数时,会使用调用栈保存函数的执行状态。
例如函数 A 调用函数 B,函数 B 又调用函数 C,那么函数 C 执行完后,会先返回到函数 B,再返回到函数 A。
这正好符合栈的 后进先出 特点。
2. 表达式求值
在计算数学表达式时,栈经常用于处理中缀表达式、后缀表达式等问题。
例如:
3 + 5 * 2
计算时需要考虑运算符优先级,栈可以帮助我们保存数字和运算符。
3. 括号匹配
判断括号是否合法,是栈的经典应用。
例如:
()[]{}
是合法的。
而:
([)]
是不合法的。
判断括号匹配时,可以遇到左括号就入栈,遇到右括号就和栈顶元素进行匹配。
4. 深度优先搜索 DFS
在图和树的遍历中,深度优先搜索可以使用栈来实现。
递归本质上也可以理解为系统自动维护了一个栈。
5. 浏览器前进后退
浏览器的后退功能也可以用栈来理解。
用户访问过的页面会依次保存,当点击后退时,会回到最近访问的上一个页面。
十二、使用栈时的注意事项
在使用栈时,需要注意以下几点:
1. 空栈不能直接取栈顶元素
错误写法:
cout << st.top() << endl;
如果此时栈为空,程序可能会出错。
推荐写法:
if (!st.empty()) {
cout << st.top() << endl;
}
2. 空栈不能直接出栈
错误写法:
st.pop();
如果栈为空,直接出栈也可能导致程序错误。
推荐写法:
if (!st.empty()) {
st.pop();
}
3. pop() 不会返回栈顶元素
很多初学者容易写出这样的代码:
int x = st.pop();
这是错误的。
因为 pop() 的作用只是删除栈顶元素,并不会返回元素。
正确写法应该是:
int x = st.top();
st.pop();
十三、总结
栈是一种非常基础但非常重要的数据结构。
它的核心特点是:
后进先出 LIFO
栈只允许在一端进行插入和删除操作,这一端称为栈顶。
栈的基本操作包括:
push() // 入栈
pop() // 出栈
top() // 获取栈顶元素
empty() // 判断是否为空
size() // 获取元素个数
在实际开发和算法题中,栈经常用于括号匹配、表达式求值、函数调用、DFS 遍历等场景。
虽然栈的结构并不复杂,但它是学习后续数据结构与算法的重要基础。掌握栈的定义和基本操作,对于理解递归、搜索、表达式处理等内容都有很大帮助。