| 上一篇 | 下一篇 |
|---|---|
| 顺序表和链表的对比 |
目 录
- 栈(Stack)
-
- 1)栈的定义与特点
- 2)栈的基本操作
- [3)C语言实现(顺序栈,Sequential Stack)](#3)C语言实现(顺序栈,Sequential Stack))
-
- [3.1)头文件 Stack.h](#3.1)头文件 Stack.h)
- [3.2)实现文件 Stack.c](#3.2)实现文件 Stack.c)
- [3.3)测试文件 main.c](#3.3)测试文件 main.c)
- 3.4)关键设计说明
- [4)C语言实现(链栈,Linked stack)](#4)C语言实现(链栈,Linked stack))
-
- 4.1)链栈的基本结构
- 4.2)常用操作
-
- [① 初始化 StackInit](#① 初始化 StackInit)
- [② 入栈 StackPush(单链表的头插法)](#② 入栈 StackPush(单链表的头插法))
- [③ 出栈并返回栈顶值 StackPop(头删法)](#③ 出栈并返回栈顶值 StackPop(头删法))
- [④ 判空 StackEmpty](#④ 判空 StackEmpty)
- [⑤ 获取大小 StackSize](#⑤ 获取大小 StackSize)
- [⑥ 销毁 StackDestroy](#⑥ 销毁 StackDestroy)
- 4.3)测试代码示例
- 4.4)注意事项
- [5)顺序栈 vs 链栈](#5)顺序栈 vs 链栈)
栈(Stack)
1)栈的定义与特点
栈是一种特殊的线性表,其限定只能在 表尾(栈顶)进行插入或删除操作。
- 栈顶(Top):进行插入和删除操作的一端(表尾端)
- 栈底(Bottom):另一端,通常不进行操作(表头端)
- 空栈:不含任何元素的栈(空表)
核心特性:后进先出(LIFO - Last In First Out),因此栈又称为后进先出的线性表。
- 最后入栈的元素最先出栈
- 类比:弹夹、羽毛球筒、函数调用栈
举例: 假设有 S = ( a 1 , a 2 , . . . , a n ) S=(a_1,a_2,...,a_n) S=(a1,a2,...,an),则称 a 1 a_1 a1 为栈底元素, a n a_n an 为栈顶元素。栈中元素按 a 1 → a n a_1→a_n a1→an 的次序进栈,退栈的第一个元素应为栈顶元素 a n a_n an 。

既然栈也是线性表,那么它也应具有顺序存储结构和链式存储结构,也就是 顺序栈 和 链栈 。
2)栈的基本操作
| 操作 | 说明 |
|---|---|
InitStack() |
初始化栈 |
DestroyStack() |
销毁栈 |
Push() |
入栈(压栈) |
Pop() |
出栈(弹栈) |
Top() |
获取栈顶元素 |
IsEmpty() |
判断栈是否为空 |
Size() |
获取栈中元素个数 |
3)C语言实现(顺序栈,Sequential Stack)
顺序栈和顺序表一样,本质上也是个大数组
顺序栈的基本结构:
c
typedef int STDataType; // 定义栈中元素类型(可根据需要修改)
/* 栈结构定义 */
typedef struct Stack {
STDataType* data; // 动态数组
int top; // 栈顶索引(指向栈顶元素的下一个位置)
int capacity; // 当前容量(MAXSIZE)
} Stack;
结构体中元素主要有三个:①动态数组、②栈顶索引(一开始是0)、③当前容量

访问某个元素应该用如下代码:
c
Stack s;
s.data[...]
3.1)头文件 Stack.h
c
#ifndef STACK_H
#define STACK_H
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
/* 定义栈中元素类型(可根据需要修改) */
typedef int STDataType;
/* 栈结构定义 */
typedef struct Stack {
STDataType* data; // 动态数组
int top; // 栈顶索引(指向栈顶元素的下一个位置,也可以指向当前栈顶元素,只是后续出栈压栈改一下就行)
int capacity; // 当前容量
} Stack;
/* 函数声明 */
void StackInit(Stack* ps); // 初始化
void StackDestroy(Stack* ps); // 销毁
void StackPush(Stack* ps, STDataType x); // 入栈
STDataType StackPop(Stack* ps); // 出栈,并返回弹出的元素
bool StackEmpty(Stack* ps); // 判断是否为空
int StackSize(Stack* ps); // 获取元素个数
#endif
3.2)实现文件 Stack.c
具体会给出各个函数是如何实现的
c
#include "Stack.h"
/* 检查并扩容 */
static void CheckCapacity(Stack* ps) {
if (ps->top == ps->capacity) {
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = (STDataType*)realloc(ps->data,
newCapacity * sizeof(STDataType));
if (tmp == NULL) {
perror("realloc failed");
exit(-1);
}
ps->data = tmp;
ps->capacity = newCapacity;
}
}
/* 初始化栈 */
void StackInit(Stack* ps) {
if (ps == NULL) return;
ps->data = NULL;
ps->top = 0; // 初始化栈顶索引为0(此时0索引处还没有值)
ps->capacity = 0;
}
/* 销毁栈 */
void StackDestroy(Stack* ps) {
if (ps == NULL) return;
free(ps->data);
ps->data = NULL;
ps->top = 0;
ps->capacity = 0;
}
/* 入栈 */
void StackPush(Stack* ps, STDataType x) {
if (ps == NULL) return;
CheckCapacity(ps);
ps->data[ps->top] = x; // 由于栈顶索引代表栈顶的下一个(未来)元素,所以这里直接复制给第top个元素
ps->top++; // 栈顶索引+1
}
/* 出栈(获取栈顶元素,并将栈顶索引-1) */
STDataType StackPop(Stack* ps) {
if (ps == NULL || StackEmpty(ps)) {
fprintf(stderr, "Stack is empty! Cannot pop.\n");
exit(-1); // 或使用 assert, 或返回特殊值(需约定)
}
ps->top--;
return ps->data[ps->top];
}
/* 判断栈是否为空 */
bool StackEmpty(Stack* ps) {
if (ps == NULL) return true;
return ps->top == 0;
}
/* 获取栈中元素个数 */
int StackSize(Stack* ps) {
if (ps == NULL) return 0;
return ps->top;
}
3.3)测试文件 main.c
c
#include "sequential_stack.h"
void TestStack() {
Stack s;
StackInit(&s);
printf("=== 栈测试开始 ===\n");
/* 测试空栈 */
printf("栈是否为空: %s\n", StackEmpty(&s) ? "是" : "否");
printf("栈大小: %d\n", StackSize(&s));
/* 入栈测试 */
printf("\n--- 入栈操作 ---\n");
for (int i = 1; i <= 5; i++) {
StackPush(&s, i * 10);
printf("入栈 %d, 栈顶元素: %d, 栈大小: %d\n",
i * 10, s.data[s.top-1], StackSize(&s));
}
// 获取栈顶元素就是:s.data[s.top-1]
/* 出栈测试 */
printf("\n--- 出栈操作 ---\n");
while (!StackEmpty(&s)) {
int val = StackPop(&s);
printf("出栈 %d\n", val);
printf("剩余大小: %d\n", StackSize(&s));
}
/* 再次测试空栈 */
printf("\n栈是否为空: %s\n", StackEmpty(&s) ? "是" : "否");
/* 销毁栈 */
StackDestroy(&s);
printf("\n=== 栈测试结束 ===\n");
}
int main() {
TestStack();
return 0;
}
运行结果为:
=== 栈测试开始 ===
栈是否为空: 是
栈大小: 0
--- 入栈操作 ---
入栈 10, 栈顶元素: 10, 栈大小: 1
入栈 20, 栈顶元素: 20, 栈大小: 2
入栈 30, 栈顶元素: 30, 栈大小: 3
入栈 40, 栈顶元素: 40, 栈大小: 4
入栈 50, 栈顶元素: 50, 栈大小: 5
--- 出栈操作 ---
出栈 50
剩余大小: 4
出栈 40
剩余大小: 3
出栈 30
剩余大小: 2
出栈 20
剩余大小: 1
出栈 10
剩余大小: 0
栈是否为空: 是
=== 栈测试结束 ===
3.4)关键设计说明
-
动态扩容
-
初始容量为0,首次入栈分配4个元素空间
-
空间不足时自动扩容为原来的2倍
-
使用
realloc实现内存重分配
-
-
栈顶指针设计
-
top指向栈顶元素的下一个位置 -
空栈时
top = 0 -
栈顶元素为
data[top - 1]
-
-
错误处理
-
对空指针进行检查
-
空栈时调用
StackTop()会报错退出 -
内存分配失败时程序终止
-
-
时间复杂度
-
所有基本操作:O(1)(均摊时间复杂度)
-
空间复杂度:O(n)
-
4)C语言实现(链栈,Linked stack)
链栈 = 头插头删的单链表
一般将单链表中的首元节点当作是栈顶(如果有哨兵头节点,那就是其下一个节点是栈顶;如果无哨兵头节点,那第一个节点就是栈顶)
核心思想 :用单链表实现栈,头插 + 头删 = 栈顶操作(所有操作都在头部进行)。
4.1)链栈的基本结构
节点定义:
c
typedef int STDataType; // 数据类型可泛化
typedef struct StackNode {
STDataType data; // 数据域
struct StackNode* next; // 指针域
} StackNode;
栈结构(推荐方案:带头指针 + 计数器):
c
typedef struct LinkStack {
StackNode* top; // 指向栈顶节点(即链表头)
int size; // 元素个数(可选,方便获取大小)
} LinkStack;
4.2)常用操作
这里没有加入哨兵头节点
① 初始化 StackInit
c
void StackInit(LinkStack* s) {
s->top = NULL;
s->size = 0;
}
② 入栈 StackPush(单链表的头插法)
c
void StackPush(LinkStack* s, STDataType x) {
StackNode* newNode = (StackNode*)malloc(sizeof(StackNode));
if (!newNode) exit(-1); // 内存分配失败
newNode->data = x;
newNode->next = s->top; // 新节点指向原栈顶
s->top = newNode; // 更新栈顶为新节点
s->size++;
}
③ 出栈并返回栈顶值 StackPop(头删法)
c
STDataType StackPop(LinkStack* s) {
if (StackEmpty(s)) {
fprintf(stderr, "Error: Pop from empty stack!\n");
exit(-1);
}
StackNode* del = s->top;
STDataType val = del->data;
s->top = del->next; // 栈顶下移
free(del); // 释放内存
s->size--;
return val;
}
④ 判空 StackEmpty
c
bool StackEmpty(LinkStack* s) {
return s->top == NULL;
}
⑤ 获取大小 StackSize
c
int StackSize(LinkStack* s) {
return s->size; // 若没存 size,需遍历链表(不推荐)
}
⑥ 销毁 StackDestroy
c
void StackDestroy(LinkStack* s) {
while (!StackEmpty(s)) {
StackPop(s); // 复用出栈逻辑
}
// s->top 已为 NULL,s->size 为 0
}
4.3)测试代码示例
linked_stack.h
c
#ifndef LINKED_STACK_H
#define LINKED_STACK_H
#include <stdbool.h>
// 定义栈元素类型(可修改为其他类型)
typedef int STDataType;
// 链栈节点结构
typedef struct StackNode {
STDataType data;
struct StackNode* next;
} StackNode;
// 链栈结构:仅需指向栈顶的指针 + 元素个数(可选但推荐)
typedef struct LinkStack {
StackNode* top; // 指向栈顶节点(即链表头)
int size; // 当前元素个数
} LinkStack;
// 函数声明
void StackInit(LinkStack* s);
void StackDestroy(LinkStack* s);
void StackPush(LinkStack* s, STDataType x);
STDataType StackPop(LinkStack* s);
bool StackEmpty(LinkStack* s);
int StackSize(LinkStack* s);
#endif // LINKED_STACK_H
linked_stack.c
c
#include "linked_stack.h"
#include <stdio.h>
#include <stdlib.h>
void StackInit(LinkStack* s) {
if (!s) return;
s->top = NULL;
s->size = 0;
}
void StackDestroy(LinkStack* s) {
if (!s) return;
while (!StackEmpty(s)) {
StackPop(s); // 复用出栈逻辑释放内存
}
// 此时 s->top == NULL, s->size == 0
}
void StackPush(LinkStack* s, STDataType x) {
if (!s) return;
StackNode* newNode = (StackNode*)malloc(sizeof(StackNode));
if (!newNode) {
fprintf(stderr, "Error: malloc failed in StackPush!\n");
exit(-1);
}
newNode->data = x;
newNode->next = s->top; // 新节点指向原栈顶
s->top = newNode; // 更新栈顶
s->size++;
}
STDataType StackPop(LinkStack* s) {
if (!s || StackEmpty(s)) {
fprintf(stderr, "Error: Pop from empty stack!\n");
exit(-1);
}
StackNode* delNode = s->top;
STDataType val = delNode->data;
s->top = delNode->next; // 栈顶下移
free(delNode); // 释放内存
s->size--;
return val;
}
bool StackEmpty(LinkStack* s) {
return s && s->top == NULL;
}
int StackSize(LinkStack* s) {
return s ? s->size : 0;
}
main.c
c
#include <stdio.h>
#include "linked_stack.h"
int main() {
LinkStack s;
StackInit(&s);
printf("=== 链栈测试开始 ===\n");
// 测试空栈
printf("栈是否为空: %s\n", StackEmpty(&s) ? "是" : "否");
printf("栈大小: %d\n", StackSize(&s));
// 入栈测试(并直接访问栈顶元素)
printf("\n--- 入栈操作 ---\n");
for (int i = 1; i <= 5; i++) {
StackPush(&s, i * 10);
// 直接通过 s.top->data 获取栈顶(因刚入栈,非空)
printf("入栈 %d, 栈顶元素: %d, 栈大小: %d\n",
i * 10, s.top->data, s.size);
}
// 出栈测试
printf("\n--- 出栈操作 ---\n");
while (!StackEmpty(&s)) {
int val = StackPop(&s);
printf("出栈 %d, 剩余大小: %d\n", val, s.size);
// 注意:出栈后若栈非空,仍可访问 s.top->data
}
// 再次测试空栈
printf("\n栈是否为空: %s\n", StackEmpty(&s) ? "是" : "否");
// 销毁栈
StackDestroy(&s);
printf("\n=== 链栈测试结束 ===\n");
return 0;
}
运行结果如下:
=== 链栈测试开始 ===
栈是否为空: 是
栈大小: 0
--- 入栈操作 ---
入栈 10, 栈顶元素: 10, 栈大小: 1
入栈 20, 栈顶元素: 20, 栈大小: 2
入栈 30, 栈顶元素: 30, 栈大小: 3
入栈 40, 栈顶元素: 40, 栈大小: 4
入栈 50, 栈顶元素: 50, 栈大小: 5
--- 出栈操作 ---
出栈 50, 剩余大小: 4
出栈 40, 剩余大小: 3
出栈 30, 剩余大小: 2
出栈 20, 剩余大小: 1
出栈 10, 剩余大小: 0
栈是否为空: 是
=== 链栈测试结束 ===
关键技巧 :在测试中可直接用
s.top->data获取栈顶(因已知非空)
4.4)注意事项
- 内存管理
- 每次
Push需malloc,每次Pop需free - 忘记
free→ 内存泄漏!
- 每次
- 空栈保护
Pop前必须检查是否为空,否则s->top->data会崩溃
- 封装性取舍
- 测试时可直接访问
s.top->data - 正式项目建议封装
StackTop()函数
- 测试时可直接访问
- 性能对比
- 链栈每次操作涉及内存分配/释放,实际速度可能慢于顺序栈
- 但胜在空间灵活
5)顺序栈 vs 链栈
| 对比项 | 顺序栈(数组) | 链栈(链表) |
|---|---|---|
| 内存分配 | 静态/需扩容 | 动态申请 |
| 栈满问题 | 可能溢出 | 不会溢出(除非内存耗尽) |
| 空间效率 | 可能浪费(预分配过大) | 按需分配 |
| 时间复杂度 | Push/Pop: O(1) | Push/Pop: O(1) |
| 扩容 | 需要重新分配内存 | 无需扩容 |
| 实现难度 | 简单 | 稍复杂(需处理指针) |
推荐:一般情况下优先使用顺序栈,除非有特殊需求(如频繁的大规模内存分配限制)。