一、栈的基本概念
栈(Stack)是一种只允许在固定一端进行数据插入和删除操作的线性数据结构。这一端称为栈顶(Top),另一端称为栈底(Bottom)。数据的插入操作称为入栈(Push),数据的删除操作称为出栈(Pop)。

1.1 栈的核心特性
- 先进后出(FILO - First In Last Out):最先入栈的元素最后出栈。
- 后进先出(LIFO - Last In First Out):最后入栈的元素最先出栈。
- 操作受限:只能在栈顶进行插入和删除,不能随机访问中间元素。
1.2 栈的抽象数据类型(ADT)定义
// 栈的抽象数据类型定义
ADT Stack {
数据对象:具有相同类型的数据元素集合
数据关系:线性关系,遵循LIFO原则
基本操作:
InitStack(&S):初始化栈
DestroyStack(&S):销毁栈
ClearStack(&S):清空栈
StackEmpty(S):判断栈是否为空
StackFull(S):判断栈是否已满(顺序栈)
GetTop(S, &e):获取栈顶元素
Push(&S, e):元素e入栈
Pop(&S, &e):栈顶元素出栈
StackLength(S):返回栈中元素个数
StackTraverse(S, visit()):遍历栈中元素
}
二、栈的存储结构
2.1 顺序栈
使用数组(连续内存空间)实现的栈称为顺序栈。
2.1.1 顺序栈的结构定义
#define MAXSIZE 100 // 栈的最大容量
typedef struct {
int data[MAXSIZE]; // 存储栈元素的数组
int top; // 栈顶指针
} SeqStack;
2.1.2 栈顶指针的四种组合方式
| 类型 | 栈顶指针指向 | 空栈条件 | 满栈条件 | 特点 |
|---|---|---|---|---|
| 满增栈 | 最后一个元素 | top = -1 | top = MAXSIZE-1 | 栈顶向高地址增长,位置有元素 |
| 空增栈 | 下一个可用位置 | top = 0 | top = MAXSIZE | 栈顶向高地址增长,位置无元素 |
| 满减栈 | 最后一个元素 | top = MAXSIZE | top = 0 | 栈顶向低地址减少,位置有元素 |
| 空减栈 | 下一个可用位置 | top = MAXSIZE-1 | top = -1 | 栈顶向低地址减少,位置无元素 |

2.1.3 顺序栈基本操作实现(以满增栈为例)
// 初始化栈
void InitStack(SeqStack *S) {
S->top = -1; // 空栈
}
// 判断栈空
int StackEmpty(SeqStack *S) {
return S->top == -1;
}
// 判断栈满
int StackFull(SeqStack *S) {
return S->top == MAXSIZE - 1;
}
// 入栈
int Push(SeqStack *S, int value) {
if (StackFull(S)) {
printf("栈已满,无法入栈\n");
return -1;
}
S->top++;
S->data[S->top] = value;
return 0;
}
// 出栈
int Pop(SeqStack *S, int *value) {
if (StackEmpty(S)) {
printf("栈为空,无法出栈\n");
return -1;
}
*value = S->data[S->top];
S->top--;
return 0;
}
// 获取栈顶元素
int GetTop(SeqStack *S, int *value) {
if (StackEmpty(S)) {
printf("栈为空\n");
return -1;
}
*value = S->data[S->top];
return 0;
}
2.2 链式栈
使用链表实现的栈称为链式栈,不需要预先分配固定大小的空间。
2.2.1 链式栈的结点定义
typedef struct StackNode {
int data; // 数据域
struct StackNode *next; // 指针域,指向下一个结点
} StackNode;
typedef struct {
StackNode *top; // 栈顶指针
int count; // 栈中元素个数
} LinkStack;
2.2.2 链式栈基本操作实现
// 初始化链式栈
void InitLinkStack(LinkStack *S) {
S->top = NULL;
S->count = 0;
}
// 判断栈空
int LinkStackEmpty(LinkStack *S) {
return S->top == NULL;
}
// 入栈(头插法)
int LinkPush(LinkStack *S, int value) {
StackNode *newNode = (StackNode *)malloc(sizeof(StackNode));
if (newNode == NULL) {
printf("内存分配失败\n");
return -1;
}
newNode->data = value;
newNode->next = S->top; // 新结点指向原栈顶
S->top = newNode; // 更新栈顶指针
S->count++;
return 0;
}
// 出栈
int LinkPop(LinkStack *S, int *value) {
if (LinkStackEmpty(S)) {
printf("栈为空,无法出栈\n");
return -1;
}
StackNode *temp = S->top;
*value = temp->data;
S->top = temp->next; // 栈顶指针下移
free(temp); // 释放原栈顶结点
S->count--;
return 0;
}
// 获取栈顶元素
int LinkGetTop(LinkStack *S, int *value) {
if (LinkStackEmpty(S)) {
printf("栈为空\n");
return -1;
}
*value = S->top->data;
return 0;
}
// 销毁链式栈
void DestroyLinkStack(LinkStack *S) {
int value;
while (!LinkStackEmpty(S)) {
LinkPop(S, &value); // 逐个出栈释放
}
}
三、栈的应用场景
3.1 函数调用栈(Call Stack)
程序执行时,函数调用、返回地址、局部变量等都保存在栈中。
// 示例:递归函数的栈帧
int factorial(int n) {
if (n <= 1) return 1; // 递归基
return n * factorial(n - 1); // 递归调用
}
// 调用过程栈帧变化:
// factorial(3) → factorial(2) → factorial(1)
// 每层调用都会在栈中创建新的栈帧
3.2 表达式求值
使用两个栈:操作数栈和运算符栈。
- 中缀表达式转后缀表达式:运算符优先级处理
- 后缀表达式求值:遇到操作数入栈,遇到运算符弹出操作数计算
// 示例:中缀表达式 "3 + 4 * 2 / (1 - 5)" 的求值过程
// 1. 转换为后缀表达式:"3 4 2 * 1 5 - / +"
// 2. 后缀表达式求值步骤:
// - 3 入栈
// - 4 入栈
// - 2 入栈
// - 遇到 *:弹出 2 和 4,计算 4*2=8,8入栈
// - 1 入栈
// - 5 入栈
// - 遇到 -:弹出 5 和 1,计算 1-5=-4,-4入栈
// - 遇到 /:弹出 -4 和 8,计算 8/(-4)=-2,-2入栈
// - 遇到 +:弹出 -2 和 3,计算 3+(-2)=1,1入栈
// 最终结果:1
3.3 括号匹配检查
// 检查括号是否匹配的算法
int checkParentheses(char *str) {
SeqStack S;
InitStack(&S);
for (int i = 0; str[i] != '\0'; i++) {
if (str[i] == '(' || str[i] == '[' || str[i] == '{') {
Push(&S, str[i]); // 左括号入栈
} else if (str[i] == ')' || str[i] == ']' || str[i] == '}') {
if (StackEmpty(&S)) {
return 0; // 栈空但遇到右括号,不匹配
}
char topChar;
Pop(&S, &topChar);
// 检查括号是否配对
if ((str[i] == ')' && topChar != '(') ||
(str[i] == ']' && topChar != '[') ||
(str[i] == '}' && topChar != '{')) {
return 0;
}
}
}
// 遍历结束后栈应为空
return StackEmpty(&S);
}
3.4 浏览器的前进后退功能
使用两个栈实现:
- 后退栈:保存访问过的页面
- 前进栈:保存后退操作中弹出的页面
3.5 撤销(Undo)和重做(Redo)功能
文本编辑器、绘图软件等常用栈实现操作历史记录。
四、栈的变体与扩展
4.1 双栈共享空间
两个栈共享同一数组空间,分别从两端向中间增长。
#define MAXSIZE 100
typedef struct {
int data[MAXSIZE];
int top1; // 栈1的栈顶指针,从0开始增长
int top2; // 栈2的栈顶指针,从MAXSIZE-1开始减少
} DoubleStack;
// 初始化双栈
void InitDoubleStack(DoubleStack *S) {
S->top1 = -1;
S->top2 = MAXSIZE;
}
// 判断栈1空
int Stack1Empty(DoubleStack *S) {
return S->top1 == -1;
}
// 判断栈2空
int Stack2Empty(DoubleStack *S) {
return S->top2 == MAXSIZE;
}
// 判断栈满(两个栈顶相遇)
int DoubleStackFull(DoubleStack *S) {
return S->top1 + 1 == S->top2;
}
// 向栈1入栈
int Push1(DoubleStack *S, int value) {
if (DoubleStackFull(S)) {
printf("双栈已满\n");
return -1;
}
S->top1++;
S->data[S->top1] = value;
return 0;
}
// 向栈2入栈
int Push2(DoubleStack *S, int value) {
if (DoubleStackFull(S)) {
printf("双栈已满\n");
return -1;
}
S->top2--;
S->data[S->top2] = value;
return 0;
}
4.2 最小栈(Min Stack)
能在O(1)时间内获取栈中最小元素的栈。
typedef struct {
SeqStack dataStack; // 数据栈
SeqStack minStack; // 最小栈,栈顶始终是当前最小值
} MinStack;
// 初始化最小栈
void InitMinStack(MinStack *S) {
InitStack(&S->dataStack);
InitStack(&S->minStack);
}
// 入栈
void MinPush(MinStack *S, int value) {
Push(&S->dataStack, value);
// 如果最小栈为空或新值≤最小栈栈顶,则入最小栈
if (StackEmpty(&S->minStack) || value <= GetTopValue(&S->minStack)) {
Push(&S->minStack, value);
}
}
// 出栈
int MinPop(MinStack *S, int *value) {
if (StackEmpty(&S->dataStack)) {
return -1;
}
Pop(&S->dataStack, value);
// 如果出栈的值等于最小栈栈顶,则最小栈也出栈
int minTop;
GetTop(&S->minStack, &minTop);
if (*value == minTop) {
int temp;
Pop(&S->minStack, &temp);
}
return 0;
}
// 获取最小值
int GetMin(MinStack *S, int *minValue) {
if (StackEmpty(&S->minStack)) {
return -1;
}
GetTop(&S->minStack, minValue);
return 0;
}
4.3 单调栈(Monotonic Stack)
栈中元素保持单调递增或递减的顺序,常用于解决"下一个更大/更小元素"问题。
// 示例:求每个元素的下一个更大元素
void nextGreaterElement(int arr[], int n, int result[]) {
SeqStack S;
InitStack(&S);
for (int i = n - 1; i >= 0; i--) {
// 弹出栈中所有小于等于当前元素的元素
while (!StackEmpty(&S) && GetTopValue(&S) <= arr[i]) {
int temp;
Pop(&S, &temp);
}
// 如果栈不为空,栈顶就是下一个更大元素
if (!StackEmpty(&S)) {
int top;
GetTop(&S, ⊤);
result[i] = top;
} else {
result[i] = -1; // 没有更大的元素
}
// 当前元素入栈
Push(&S, arr[i]);
}
}
五、栈的算法题常见考点
5.1 栈的基本操作
- 实现栈的Push、Pop、GetMin等操作
- 使用两个栈实现队列
- 使用队列实现栈
5.2 表达式相关
- 中缀表达式转后缀表达式
- 后缀表达式求值
- 计算器实现(支持加减乘除和括号)
5.3 括号匹配与验证
- 有效的括号
- 最长有效括号子串
- 删除无效的括号
5.4 单调栈应用
- 柱状图中最大的矩形
- 接雨水问题
- 每日温度(下一个更高温度)
- 下一个更大元素I/II
5.5 递归与栈
- 二叉树的前序、中序、后序遍历(迭代实现)
- 二叉树的层序遍历
- 图的深度优先搜索(DFS)
六、栈的性能分析与优化
6.1 时间复杂度分析
| 操作 | 顺序栈 | 链式栈 |
|---|---|---|
| 入栈(Push) | O(1) | O(1) |
| 出栈(Pop) | O(1) | O(1) |
| 获取栈顶(Top) | O(1) | O(1) |
| 判断栈空 | O(1) | O(1) |
| 遍历栈 | O |

4507

被折叠的 条评论
为什么被折叠?



