数据结构 - 栈
栈(Stack)是一种遵循后进先出(Last In First Out,LIFO)原则的线性数据结构,可以形象地理解为一摞叠放的盘子——只能从最上面放入,也只能从最上面取走。
栈的概念与原理
栈 (Stack) — LIFO 原理示意
栈顶 (Top) — 只能从这里操作
盘 1 (栈底)
盘 2
盘 3
盘 4 (栈顶)
栈底 (Bottom) — 最后才能取出
▲ push (入栈)
▼ pop (出栈)
LIFO 验证:入栈 10→20→30,出栈 30→20→10(后进先出)| 栈只关心栈顶,不关心中间
数组实现 (顺序栈)
存储:固定大小数组 + top 指针
push:items[++top] = value
pop:return items[top--]
优点:访问快,缓存友好,实现简单
缺点:容量固定,可能溢出
链表实现 (链式栈)
存储:动态节点 + 栈顶指针(链表头)
push:头插法 newNode->next = top
pop:top = top->next; free(旧头)
优点:无容量限制,灵活动态扩展
缺点:额外指针开销,缓存不友好
数组实现栈
实例
#include <stdio.h>
#include <stdbool.h> /* bool 类型 */
#define MAX_SIZE 100 /* 栈的最大容量 */
/* 栈结构体:包含数据数组和栈顶指针 */
struct Stack {
int items[MAX_SIZE]; /* 存储栈元素的数组 */
int top; /* 栈顶指针,-1 表示栈空 */
};
/* 初始化栈:将 top 置为 -1 */
void initStack(struct Stack* s) {
s->top = -1;
}
/* 判空:top 为 -1 时栈为空 */
bool isEmpty(struct Stack* s) {
return s->top == -1;
}
/* 判满:top 到达容量上限 -1 时栈满 */
bool isFull(struct Stack* s) {
return s->top == MAX_SIZE - 1;
}
/* 入栈 (push):将元素放入栈顶
先移动 top,再写入数据 */
void push(struct Stack* s, int value) {
if (isFull(s)) {
printf("栈已满,无法入栈!\n");
return;
}
s->items[++s->top] = value; /* top 先加 1,再赋值 */
printf("入栈: %d\n", value);
}
/* 出栈 (pop):移除并返回栈顶元素
先取出数据,再移动 top */
int pop(struct Stack* s) {
if (isEmpty(s)) {
printf("栈为空,无法出栈!\n");
return -1; /* 返回特殊值表示错误 */
}
return s->items[s->top--]; /* 先返回值,top 再减 1 */
}
/* 查看栈顶 (peek):不弹出,只查看 */
int peek(struct Stack* s) {
if (isEmpty(s)) {
printf("栈为空!\n");
return -1;
}
return s->items[s->top];
}
int main() {
struct Stack s;
initStack(&s);
push(&s, 10); /* 入栈: 10 */
push(&s, 20); /* 入栈: 20 */
push(&s, 30); /* 入栈: 30 */
printf("栈顶元素: %d\n", peek(&s)); /* 输出: 栈顶元素: 30 */
printf("出栈: %d\n", pop(&s)); /* 输出: 出栈: 30 (后进先出) */
printf("出栈: %d\n", pop(&s)); /* 输出: 出栈: 20 */
printf("出栈: %d\n", pop(&s)); /* 输出: 出栈: 10 */
if (isEmpty(&s)) {
printf("栈已空\n"); /* 输出: 栈已空 */
}
return 0;
}
#include <stdbool.h> /* bool 类型 */
#define MAX_SIZE 100 /* 栈的最大容量 */
/* 栈结构体:包含数据数组和栈顶指针 */
struct Stack {
int items[MAX_SIZE]; /* 存储栈元素的数组 */
int top; /* 栈顶指针,-1 表示栈空 */
};
/* 初始化栈:将 top 置为 -1 */
void initStack(struct Stack* s) {
s->top = -1;
}
/* 判空:top 为 -1 时栈为空 */
bool isEmpty(struct Stack* s) {
return s->top == -1;
}
/* 判满:top 到达容量上限 -1 时栈满 */
bool isFull(struct Stack* s) {
return s->top == MAX_SIZE - 1;
}
/* 入栈 (push):将元素放入栈顶
先移动 top,再写入数据 */
void push(struct Stack* s, int value) {
if (isFull(s)) {
printf("栈已满,无法入栈!\n");
return;
}
s->items[++s->top] = value; /* top 先加 1,再赋值 */
printf("入栈: %d\n", value);
}
/* 出栈 (pop):移除并返回栈顶元素
先取出数据,再移动 top */
int pop(struct Stack* s) {
if (isEmpty(s)) {
printf("栈为空,无法出栈!\n");
return -1; /* 返回特殊值表示错误 */
}
return s->items[s->top--]; /* 先返回值,top 再减 1 */
}
/* 查看栈顶 (peek):不弹出,只查看 */
int peek(struct Stack* s) {
if (isEmpty(s)) {
printf("栈为空!\n");
return -1;
}
return s->items[s->top];
}
int main() {
struct Stack s;
initStack(&s);
push(&s, 10); /* 入栈: 10 */
push(&s, 20); /* 入栈: 20 */
push(&s, 30); /* 入栈: 30 */
printf("栈顶元素: %d\n", peek(&s)); /* 输出: 栈顶元素: 30 */
printf("出栈: %d\n", pop(&s)); /* 输出: 出栈: 30 (后进先出) */
printf("出栈: %d\n", pop(&s)); /* 输出: 出栈: 20 */
printf("出栈: %d\n", pop(&s)); /* 输出: 出栈: 10 */
if (isEmpty(&s)) {
printf("栈已空\n"); /* 输出: 栈已空 */
}
return 0;
}
链表实现栈
实例
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
/* 链表节点 */
struct Node {
int data;
struct Node* next;
};
/* 入栈 (push):以链表头作为栈顶,头插法
时间复杂度:O(1) */
struct Node* push(struct Node* top, int value) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = value;
newNode->next = top; /* 新节点指向原栈顶 */
printf("入栈: %d\n", value);
return newNode; /* 新节点成为新栈顶 */
}
/* 出栈 (pop):移除链表头节点
时间复杂度:O(1) */
struct Node* pop(struct Node* top) {
if (top == NULL) {
printf("栈为空!\n");
return NULL;
}
struct Node* temp = top;
printf("出栈: %d\n", top->data);
top = top->next; /* 栈顶后移 */
free(temp); /* 释放旧栈顶 */
return top;
}
/* 判空 */
bool isEmpty(struct Node* top) {
return top == NULL;
}
int main() {
struct Node* stackTop = NULL; /* 初始时栈为空 */
stackTop = push(stackTop, 100); /* 入栈: 100 */
stackTop = push(stackTop, 200); /* 入栈: 200 */
stackTop = push(stackTop, 300); /* 入栈: 300 */
printf("栈顶: %d\n", stackTop->data); /* 输出: 栈顶: 300 */
stackTop = pop(stackTop); /* 出栈: 300 */
stackTop = pop(stackTop); /* 出栈: 200 */
stackTop = pop(stackTop); /* 出栈: 100 */
if (isEmpty(stackTop)) {
printf("栈已空\n"); /* 输出: 栈已空 */
}
return 0;
}
#include <stdlib.h>
#include <stdbool.h>
/* 链表节点 */
struct Node {
int data;
struct Node* next;
};
/* 入栈 (push):以链表头作为栈顶,头插法
时间复杂度:O(1) */
struct Node* push(struct Node* top, int value) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = value;
newNode->next = top; /* 新节点指向原栈顶 */
printf("入栈: %d\n", value);
return newNode; /* 新节点成为新栈顶 */
}
/* 出栈 (pop):移除链表头节点
时间复杂度:O(1) */
struct Node* pop(struct Node* top) {
if (top == NULL) {
printf("栈为空!\n");
return NULL;
}
struct Node* temp = top;
printf("出栈: %d\n", top->data);
top = top->next; /* 栈顶后移 */
free(temp); /* 释放旧栈顶 */
return top;
}
/* 判空 */
bool isEmpty(struct Node* top) {
return top == NULL;
}
int main() {
struct Node* stackTop = NULL; /* 初始时栈为空 */
stackTop = push(stackTop, 100); /* 入栈: 100 */
stackTop = push(stackTop, 200); /* 入栈: 200 */
stackTop = push(stackTop, 300); /* 入栈: 300 */
printf("栈顶: %d\n", stackTop->data); /* 输出: 栈顶: 300 */
stackTop = pop(stackTop); /* 出栈: 300 */
stackTop = pop(stackTop); /* 出栈: 200 */
stackTop = pop(stackTop); /* 出栈: 100 */
if (isEmpty(stackTop)) {
printf("栈已空\n"); /* 输出: 栈已空 */
}
return 0;
}
链表实现栈的优势在于理论上没有固定容量限制,只受限于系统可用内存。但每个节点需要额外的指针存储空间。
栈的应用场景
| 应用场景 | 说明 | 为什么用栈 |
|---|---|---|
| 函数调用栈 | 保存函数的返回地址和局部变量 | 函数调用是嵌套的,天然 LIFO |
| 括号匹配 | 检查代码中括号是否正确配对 | 遇到左括号入栈,右括号与栈顶匹配出栈 |
| 撤销操作 | 文本编辑器、浏览器的撤销功能 | 每次操作入栈,撤销时出栈 |
| 表达式求值 | 计算中缀/后缀表达式的值 | 操作数入栈,运算符计算后结果入栈 |
| 深度优先搜索 | 图的 DFS 遍历算法 | 显式使用栈或递归(隐式使用调用栈) |
