S8 链式栈
3.3链式栈
3.3.1链式栈的核心特性
首先通过表格对比链式栈与顺序栈的差异:
特性对比 | 顺序栈 | 链式栈 |
|---|---|---|
存储结构 | 数组(连续内存) | 链表(离散内存) |
内存分配 | 静态/动态数组 | 动态节点分配 |
扩容方式 | 需要重新分配内存 | 按需分配节点,无需扩容 |
空间效率 | 可能有空间浪费 | 每个节点有指针开销 |
访问效率 | O(1)随机访问 | O(n)顺序访问 |
溢出风险 | 栈满时溢出 | 几乎不会溢出(除非内存耗尽) |
3.3.2链式栈的结构
链式栈的结构定义
typedef struct stacknode {int val; // 数据域struct stacknode* next; // 指针域} stacknode;typedef struct stacklist {stacknode* top; // 栈顶指针int count; // 元素个数} stacklist;关键特性:
- 动态节点:每个节点独立分配内存,通过指针连接
- 栈顶指针: top 指向链表的第一个节点(栈顶元素)
- 不带头节点:第一个节点就是存储实际数据的节点
- 计数记录: count 记录元素个数,避免遍历计数
函数详细讲解
1. 初始化函数 ( initstack )
void initstack(stacklist* p) {assert(p != NULL);p->count = 0;p->top = NULL;}代码解读:
- 初始化空栈: top = NULL 表示栈顶为空
- count = 0 表示元素个数为零
- 这是不带头节点的链式栈,结构简洁
2. 节点管理函数
创建节点 ( buynode )
stacknode* buynode() {stacknode* p = (stacknode*)malloc(sizeof(stacknode));if (p == NULL) return NULL;return p;}释放节点 ( freenode )
void freenode(stacknode* p) {assert(p != NULL);free(p);p = NULL; // 注意:这里只是修改局部变量,不影响外部指针}关键点:
- 释放单个节点的内存
- p = NULL 只在函数内有效,调用方需要自行置空指针
3. 核心栈操作
入栈操作 ( push )
bool push(stacklist* p, int val) {assert(p != NULL);stacknode* newnode = buynode();if (newnode == NULL) return false;newnode->val = val;newnode->next = p->top; // 新节点指向原栈顶p->top = newnode; // 更新栈顶指针p->count++;return true;}图解入栈过程:
入栈前: top → A → B → NULL入栈C: top → C → A → B → NULL步骤:1. C->next = top (即A)2. top = C关键点:
- 时间复杂度O(1):效率很高
- 头插法:新节点总是插入在链表头部
- 动态扩容:无需担心栈满,除非内存耗尽
出栈操作 ( pop )
bool pop(stacklist* p, int* pval) {assert(p != NULL);if(is_Empty(p)) return false;*pval = p->top->val; // 保存栈顶值stacknode* temp = p->top; // 保存要删除的节点p->top = p->top->next; // 栈顶指针下移freenode(temp); // 释放原栈顶节点p->count--;return true;}图解出栈过程:
出栈前: top → C → A → B → NULL出栈C: top → A → B → NULL步骤:1. 保存C的值2. top = C->next (即A)3. free(C)关键点:
- 时间复杂度O(1):效率很高
- 需要保存返回值:通过参数返回被删除的栈顶值
- 内存管理:及时释放节点内存,避免泄漏
4. 辅助操作函数
获取栈顶元素 ( gettop )
bool gettop(stacklist* p, int* pval) {if (is_Empty(p)) return false;*pval = p->top->val; // 获取栈顶值但不删除return true;}关键点:
- 只读操作:不修改栈的状态
- 时间复杂度O(1):直接访问栈顶节点
- 与pop的区别:不删除节点,只是查看栈顶值
判空函数 ( is_Empty )
bool is_Empty(stacklist* p) {if (p->count == 0) return true;return false;// 或者判断:return p->top == NULL;}获取栈大小 ( StackLength )
int StackLength(stacklist* p) {assert(p != NULL);return p->count; // 直接返回计数,O(1)时间复杂度}优势:由于维护了 count 计数器,获取大小的时间复杂度是O(1),比遍历链表计数(O(n))高效得多。
5. 遍历与销毁函数
遍历打印 ( PrintInto )
void PrintInto(const stacklist* p) {assert(p != NULL);stacknode* current = p->top;while (current != NULL) {printf("%d ", current->val);current = current->next;}}代码解读:
- 从栈顶到栈底顺序打印(与入栈顺序相反)
- 时间复杂度O(n):需要访问每个节点
- 不修改栈结构:只是读取数据
清空栈 ( ClearElem )
void ClearElem(stacklist* p) {assert(p != NULL);while (p->top != NULL) {stacknode* temp = p->top;p->top = p->top->next;freenode(temp);}p->count = 0;}销毁栈 ( DestroySeqList )
void DestroySeqList(stacklist* p) {assert(p != NULL);while (p->top != NULL) {stacknode* temp = p->top;p->top = p->top->next;freenode(temp);}p->count = 0;}关键点:
- 释放所有节点:避免内存泄漏
- 重置计数器: count = 0
- 栈顶指针:执行后 top 为 NULL ,回到初始状态
3.3.3链式栈的优势总结
核心优势
- 真正的动态性
- 内存完全动态分配,无需预估最大容量
- 几乎不会出现栈满情况(除非系统内存耗尽)
- 高效的核心操作
- 入栈和出栈操作的时间复杂度都是O(1)
- 只需要修改指针,不需要移动数据
- 内存使用灵活
- 每个节点独立分配,内存利用率高
- 适合内存碎片化严重的环境
时间复杂度总结
操作 | 时间复杂度 | 说明 |
|---|---|---|
初始化 | O(1) | 设置指针和计数器 |
入栈 | O(1) | 修改指针连接 |
出栈 | O(1) | 修改指针连接 |
获取栈顶 | O(1) | 直接访问栈顶节点 |
判空/大小 | O(1) | 使用计数器 |
遍历 | O(n) | 需要访问每个节点 |
清空/销毁 | O(n) | 需要释放每个节点 |
适用场景推荐
- 栈大小不可预知或变化很大的场景
- 内存受限环境:避免一次性分配大块内存
- 需要频繁动态调整栈容量的应用程序
- 实现递归函数的调用栈(系统自动管理)
- 算法实现:如深度优先搜索、表达式求值等
