一、栈的基本概念整理
1. 栈的定义与特性
栈(Stack)是一种线性数据结构 ,遵循后进先出(LIFO) 或先进后出(FILO) 的原则。
text
入栈顺序:A → B → C → D
出栈顺序:D → C → B → A
2. 栈、队列和表的对比
| 特性 | 栈 | 队列 | 表(链表/数组) |
|---|---|---|---|
| 插入位置 | 只能在栈顶 | 只能在队尾 | 任意位置 |
| 删除位置 | 只能在栈顶 | 只能在队首 | 任意位置 |
| 访问特性 | 只能访问栈顶 | 只能访问队首队尾 | 任意位置访问 |
| 操作原则 | 后进先出(LIFO) | 先进先出(FIFO) | 任意顺序 |
| 典型应用 | 函数调用、括号匹配 | 任务调度、消息队列 | 通用数据存储 |
二、顺序栈(空增栈类型)的详细实现
1. 空增栈的特点
-
空栈:栈顶指针指向下一个入栈位置
-
增栈:栈向高地址方向增长
2. 数据结构定义
c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_SIZE 100 // 栈的最大容量
typedef struct {
int data[MAX_SIZE]; // 存储栈元素的数组
int top; // 栈顶指针,指向下一个入栈位置
} SeqStack;
3. 栈的四种状态图示
text
初始状态(栈空):
top=0
┌───┬───┬───┬───┬───┐
│ │ │ │ │ │
└───┴───┴───┴───┴───┘
0 1 2 3 4 ← 数组下标
↑
栈底 栈顶方向→
入栈10后:
top=1
┌───┬───┬───┬───┬───┐
│10 │ │ │ │ │
└───┴───┴───┴───┴───┘
↑ ↑
栈底 栈顶方向→
再入栈20、30后:
top=3
┌───┬───┬───┬───┬───┐
│10 │20 │30 │ │ │
└───┴───┴───┴───┴───┘
↑ ↑
栈底 栈顶方向→
栈满状态:
top=MAX_SIZE
┌───┬───┬───┬───┬───┐
│10 │20 │30 │...│99 │
└───┴───┴───┴───┴───┘
↑ ↑
栈底 栈顶方向→
4. 完整操作实现
c
// 1. 初始化栈
void InitSeqStack(SeqStack *s) {
s->top = 0; // 空增栈:top指向下一个入栈位置
printf("初始化空栈,top = %d\n", s->top);
}
// 2. 判断栈是否为空
bool IsEmptySeqStack(SeqStack *s) {
return s->top == 0;
}
// 3. 判断栈是否已满
bool IsFullSeqStack(SeqStack *s) {
return s->top == MAX_SIZE;
}
// 4. 获取栈中元素个数
int GetSizeSeqStack(SeqStack *s) {
return s->top;
}
// 5. 入栈操作
bool PushSeqStack(SeqStack *s, int value) {
if (IsFullSeqStack(s)) {
printf("栈已满,无法入栈元素 %d\n", value);
return false;
}
// 关键操作:先赋值,后移动栈顶指针
s->data[s->top] = value;
s->top++;
printf("入栈: %d, 栈顶指针: top = %d\n", value, s->top);
return true;
}
// 6. 出栈操作
bool PopSeqStack(SeqStack *s, int *value) {
if (IsEmptySeqStack(s)) {
printf("栈已空,无法出栈\n");
return false;
}
// 关键操作:先移动栈顶指针,后取值
s->top--;
*value = s->data[s->top];
printf("出栈: %d, 栈顶指针: top = %d\n", *value, s->top);
return true;
}
// 7. 获取栈顶元素(不出栈)
bool PeekSeqStack(SeqStack *s, int *value) {
if (IsEmptySeqStack(s)) {
printf("栈已空,无栈顶元素\n");
return false;
}
// 注意:top指向下一个入栈位置,所以栈顶元素在top-1位置
*value = s->data[s->top - 1];
return true;
}
// 8. 清空栈
void ClearSeqStack(SeqStack *s) {
s->top = 0;
printf("栈已清空,top = %d\n", s->top);
}
// 9. 遍历栈(从栈底到栈顶)
void TraverseSeqStack(SeqStack *s) {
if (IsEmptySeqStack(s)) {
printf("栈为空\n");
return;
}
printf("栈元素(栈底 → 栈顶): ");
for (int i = 0; i < s->top; i++) {
printf("%d ", s->data[i]);
}
printf("\n");
}
// 10. 遍历栈(从栈顶到栈底)
void ReverseTraverseSeqStack(SeqStack *s) {
if (IsEmptySeqStack(s)) {
printf("栈为空\n");
return;
}
printf("栈元素(栈顶 → 栈底): ");
for (int i = s->top - 1; i >= 0; i--) {
printf("%d ", s->data[i]);
}
printf("\n");
}
5. 使用示例与测试
c
// 测试函数
void TestSeqStack() {
printf("===== 测试顺序栈(空增栈) =====\n\n");
SeqStack stack;
// 1. 初始化栈
InitSeqStack(&stack);
// 2. 入栈操作测试
printf("1. 入栈操作:\n");
for (int i = 1; i <= 5; i++) {
PushSeqStack(&stack, i * 10);
}
TraverseSeqStack(&stack);
ReverseTraverseSeqStack(&stack);
printf("当前栈大小: %d\n\n", GetSizeSeqStack(&stack));
// 3. 出栈操作测试
printf("2. 出栈操作:\n");
int popped;
PopSeqStack(&stack, &popped);
printf("出栈元素: %d\n", popped);
TraverseSeqStack(&stack);
printf("当前栈大小: %d\n\n", GetSizeSeqStack(&stack));
// 4. 查看栈顶元素
printf("3. 查看栈顶元素:\n");
int topValue;
if (PeekSeqStack(&stack, &topValue)) {
printf("栈顶元素: %d\n", topValue);
}
TraverseSeqStack(&stack);
printf("\n");
// 5. 栈空和栈满测试
printf("4. 栈状态测试:\n");
printf("栈是否为空: %s\n", IsEmptySeqStack(&stack) ? "是" : "否");
printf("栈是否已满: %s\n", IsFullSeqStack(&stack) ? "是" : "否");
printf("\n");
// 6. 清空栈测试
printf("5. 清空栈:\n");
ClearSeqStack(&stack);
printf("栈是否为空: %s\n", IsEmptySeqStack(&stack) ? "是" : "否");
printf("\n");
// 7. 栈满测试
printf("6. 栈满测试:\n");
for (int i = 1; i <= MAX_SIZE + 2; i++) {
if (!PushSeqStack(&stack, i)) {
break; // 栈满时停止
}
}
printf("最终栈大小: %d\n", GetSizeSeqStack(&stack));
}
int main() {
TestSeqStack();
return 0;
}
6. 输出结果示例
text
===== 测试顺序栈(空增栈) =====
1. 入栈操作:
入栈: 10, 栈顶指针: top = 1
入栈: 20, 栈顶指针: top = 2
入栈: 30, 栈顶指针: top = 3
入栈: 40, 栈顶指针: top = 4
入栈: 50, 栈顶指针: top = 5
栈元素(栈底 → 栈顶): 10 20 30 40 50
栈元素(栈顶 → 栈底): 50 40 30 20 10
当前栈大小: 5
2. 出栈操作:
出栈: 50, 栈顶指针: top = 4
出栈元素: 50
栈元素(栈底 → 栈顶): 10 20 30 40
当前栈大小: 4
3. 查看栈顶元素:
栈顶元素: 40
栈元素(栈底 → 栈顶): 10 20 30 40
4. 栈状态测试:
栈是否为空: 否
栈是否已满: 否
5. 清空栈:
栈已清空,top = 0
栈是否为空: 是
6. 栈满测试:
入栈: 1, 栈顶指针: top = 1
...(省略中间过程)...
入栈: 100, 栈顶指针: top = 100
栈已满,无法入栈元素 101
最终栈大小: 100
三、链式栈(无头结点,空栈类型)的详细实现
1. 链式栈的特点
-
无头结点:栈顶指针直接指向栈顶元素
-
空栈类型:栈顶指针为NULL表示栈空
-
动态分配:无需预先指定栈的大小
2. 数据结构定义
c
// 栈节点定义
typedef struct StackNode {
int data; // 数据域
struct StackNode *next; // 指针域,指向下一个节点
} StackNode;
// 链式栈定义
typedef struct {
StackNode *top; // 栈顶指针,指向栈顶元素
int size; // 栈中元素个数
} LinkStack;
3. 内存结构图示
text
初始状态(栈空):
top → NULL
size = 0
入栈10后:
top → [10|NULL]
size = 1
再入栈20后:
top → [20|∙] → [10|NULL]
size = 2
再入栈30后:
top → [30|∙] → [20|∙] → [10|NULL]
size = 3
出栈后(弹出30):
top → [20|∙] → [10|NULL]
size = 2
4. 完整操作实现
c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 1. 初始化栈
void InitLinkStack(LinkStack *s) {
s->top = NULL;
s->size = 0;
printf("初始化链式栈,top = NULL, size = 0\n");
}
// 2. 判断栈是否为空
bool IsEmptyLinkStack(LinkStack *s) {
return s->top == NULL;
}
// 3. 获取栈中元素个数
int GetSizeLinkStack(LinkStack *s) {
return s->size;
}
// 4. 创建新节点
StackNode* CreateNode(int value) {
StackNode *node = (StackNode*)malloc(sizeof(StackNode));
if (node == NULL) {
printf("内存分配失败\n");
return NULL;
}
node->data = value;
node->next = NULL;
return node;
}
// 5. 入栈操作(头插法)
bool PushLinkStack(LinkStack *s, int value) {
// 创建新节点
StackNode *newNode = CreateNode(value);
if (newNode == NULL) {
return false;
}
// 新节点的next指向原栈顶
newNode->next = s->top;
// 更新栈顶指针
s->top = newNode;
s->size++;
printf("入栈: %d, 栈大小: %d\n", value, s->size);
return true;
}
// 6. 出栈操作
bool PopLinkStack(LinkStack *s, int *value) {
if (IsEmptyLinkStack(s)) {
printf("栈已空,无法出栈\n");
return false;
}
// 保存栈顶节点
StackNode *temp = s->top;
// 获取栈顶数据
*value = temp->data;
// 更新栈顶指针
s->top = temp->next;
// 释放原栈顶节点
free(temp);
s->size--;
printf("出栈: %d, 栈大小: %d\n", *value, s->size);
return true;
}
// 7. 获取栈顶元素(不出栈)
bool PeekLinkStack(LinkStack *s, int *value) {
if (IsEmptyLinkStack(s)) {
printf("栈已空,无栈顶元素\n");
return false;
}
*value = s->top->data;
return true;
}
// 8. 清空栈
void ClearLinkStack(LinkStack *s) {
int value;
printf("清空栈中元素: ");
while (!IsEmptyLinkStack(s)) {
PopLinkStack(s, &value);
printf("%d ", value);
}
printf("\n栈已清空,size = %d\n", s->size);
}
// 9. 销毁栈(完全释放内存)
void DestroyLinkStack(LinkStack *s) {
ClearLinkStack(s);
printf("链式栈已销毁\n");
}
// 10. 遍历栈(从栈顶到栈底)
void TraverseLinkStack(LinkStack *s) {
if (IsEmptyLinkStack(s)) {
printf("栈为空\n");
return;
}
printf("栈元素(栈顶 → 栈底): ");
StackNode *current = s->top;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
// 11. 遍历栈(从栈底到栈顶)- 使用递归或辅助栈
void ReverseTraverseLinkStack(LinkStack *s) {
if (IsEmptyLinkStack(s)) {
printf("栈为空\n");
return;
}
// 使用辅助栈实现逆序输出
LinkStack tempStack;
InitLinkStack(&tempStack);
StackNode *current = s->top;
while (current != NULL) {
PushLinkStack(&tempStack, current->data);
current = current->next;
}
printf("栈元素(栈底 → 栈顶): ");
current = tempStack.top;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
// 12. 检查栈中是否包含某个元素
bool ContainsLinkStack(LinkStack *s, int value) {
StackNode *current = s->top;
while (current != NULL) {
if (current->data == value) {
return true;
}
current = current->next;
}
return false;
}
// 13. 复制栈(深拷贝)
bool CopyLinkStack(LinkStack *src, LinkStack *dest) {
// 清空目标栈
ClearLinkStack(dest);
// 如果源栈为空,直接返回
if (IsEmptyLinkStack(src)) {
return true;
}
// 使用辅助栈实现逆序复制
LinkStack tempStack;
InitLinkStack(&tempStack);
// 将源栈元素压入临时栈(实现逆序)
StackNode *current = src->top;
while (current != NULL) {
PushLinkStack(&tempStack, current->data);
current = current->next;
}
// 将临时栈元素压入目标栈(恢复原顺序)
current = tempStack.top;
while (current != NULL) {
PushLinkStack(dest, current->data);
current = current->next;
}
return true;
}
5. 使用示例与测试
c
// 测试链式栈的函数
void TestLinkStack() {
printf("===== 测试链式栈(无头结点) =====\n\n");
LinkStack stack;
// 1. 初始化栈
InitLinkStack(&stack);
// 2. 入栈操作测试
printf("1. 入栈操作:\n");
for (int i = 1; i <= 5; i++) {
PushLinkStack(&stack, i * 10);
}
TraverseLinkStack(&stack);
ReverseTraverseLinkStack(&stack);
printf("当前栈大小: %d\n\n", GetSizeLinkStack(&stack));
// 3. 出栈操作测试
printf("2. 出栈操作:\n");
int popped;
PopLinkStack(&stack, &popped);
printf("出栈元素: %d\n", popped);
TraverseLinkStack(&stack);
printf("当前栈大小: %d\n\n", GetSizeLinkStack(&stack));
// 4. 查看栈顶元素
printf("3. 查看栈顶元素:\n");
int topValue;
if (PeekLinkStack(&stack, &topValue)) {
printf("栈顶元素: %d\n", topValue);
}
TraverseLinkStack(&stack);
printf("\n");
// 5. 栈空测试
printf("4. 栈状态测试:\n");
printf("栈是否为空: %s\n", IsEmptyLinkStack(&stack) ? "是" : "否");
printf("\n");
// 6. 检查元素是否存在
printf("5. 检查元素是否存在:\n");
printf("栈中是否包含20: %s\n", ContainsLinkStack(&stack, 20) ? "是" : "否");
printf("栈中是否包含50: %s\n", ContainsLinkStack(&stack, 50) ? "是" : "否");
printf("\n");
// 7. 复制栈测试
printf("6. 复制栈测试:\n");
LinkStack copiedStack;
InitLinkStack(&copiedStack);
CopyLinkStack(&stack, &copiedStack);
printf("原始栈: ");
TraverseLinkStack(&stack);
printf("复制栈: ");
TraverseLinkStack(&copiedStack);
printf("\n");
// 8. 清空栈测试
printf("7. 清空栈:\n");
ClearLinkStack(&stack);
printf("栈是否为空: %s\n", IsEmptyLinkStack(&stack) ? "是" : "否");
printf("\n");
// 9. 销毁栈
printf("8. 销毁栈:\n");
DestroyLinkStack(&stack);
}
// 栈的应用:表达式求值(简单示例)
int EvaluateExpression(const char* expr) {
LinkStack operandStack; // 操作数栈
LinkStack operatorStack; // 操作符栈
InitLinkStack(&operandStack);
InitLinkStack(&operatorStack);
// 这里只是演示栈的应用,完整的表达式求值需要更复杂的实现
printf("表达式: %s\n", expr);
// 简单处理:假设表达式只有数字和+,-运算符,没有括号
int result = 0;
int currentNumber = 0;
for (int i = 0; expr[i] != '\0'; i++) {
if (expr[i] >= '0' && expr[i] <= '9') {
currentNumber = currentNumber * 10 + (expr[i] - '0');
} else if (expr[i] == '+' || expr[i] == '-') {
PushLinkStack(&operandStack, currentNumber);
currentNumber = 0;
PushLinkStack(&operatorStack, expr[i]);
}
}
// 处理最后一个数字
if (currentNumber != 0) {
PushLinkStack(&operandStack, currentNumber);
}
// 简单计算:这里只是演示,实际需要按照运算符优先级计算
printf("操作数栈大小: %d\n", GetSizeLinkStack(&operandStack));
printf("操作符栈大小: %d\n", GetSizeLinkStack(&operatorStack));
DestroyLinkStack(&operandStack);
DestroyLinkStack(&operatorStack);
return result;
}
int main() {
TestLinkStack();
printf("\n===== 栈的应用示例 =====\n");
EvaluateExpression("3+5-2");
return 0;
}
6. 输出结果示例
text
===== 测试链式栈(无头结点) =====
1. 入栈操作:
入栈: 10, 栈大小: 1
入栈: 20, 栈大小: 2
入栈: 30, 栈大小: 3
入栈: 40, 栈大小: 4
入栈: 50, 栈大小: 5
栈元素(栈顶 → 栈底): 50 40 30 20 10
栈元素(栈底 → 栈顶): 10 20 30 40 50
当前栈大小: 5
2. 出栈操作:
出栈: 50, 栈大小: 4
出栈元素: 50
栈元素(栈顶 → 栈底): 40 30 20 10
当前栈大小: 4
3. 查看栈顶元素:
栈顶元素: 40
栈元素(栈顶 → 栈底): 40 30 20 10
4. 栈状态测试:
栈是否为空: 否
5. 检查元素是否存在:
栈中是否包含20: 是
栈中是否包含50: 否
6. 复制栈测试:
清空栈中元素: 10 20 30 40
栈已清空,size = 0
原始栈: 栈元素(栈顶 → 栈底): 40 30 20 10
复制栈: 栈元素(栈顶 → 栈底): 40 30 20 10
7. 清空栈:
清空栈中元素: 10 20 30 40
栈已清空,size = 0
栈是否为空: 是
8. 销毁栈:
清空栈中元素:
栈已清空,size = 0
链式栈已销毁
===== 栈的应用示例 =====
表达式: 3+5-2
操作数栈大小: 3
操作符栈大小: 2
清空栈中元素: 2 5 3
栈已清空,size = 0
链式栈已销毁
清空栈中元素: 45 43
栈已清空,size = 0
链式栈已销毁
四、顺序栈与链式栈的对比总结
| 特性 | 顺序栈(空增栈) | 链式栈(无头结点) |
|---|---|---|
| 存储结构 | 数组(连续内存) | 链表(离散内存) |
| 容量 | 固定大小 | 动态扩展 |
| 空间效率 | 存储密度高 | 有指针开销 |
| 时间效率 | O(1) 访问 | O(1) 插入删除 |
| 内存分配 | 编译时确定 | 运行时动态分配 |
| 栈满判断 | top == MAX_SIZE | 内存耗尽时失败 |
| 栈空判断 | top == 0 | top == NULL |
| 适用场景 | 栈大小可预估 | 栈大小变化大 |
| 优点 | 实现简单,访问快 | 灵活,无大小限制 |
| 缺点 | 容量固定,可能溢出 | 有指针开销,内存碎片 |
五、栈的常见应用场景
-
函数调用栈:保存函数调用信息
-
括号匹配:检查表达式括号是否匹配
-
表达式求值:中缀转后缀,后缀表达式求值
-
浏览器的前进后退:使用两个栈实现
-
撤销操作:编辑器中的撤销功能
-
递归算法:保存递归调用信息