ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

数据结构栈

2026/10/5 4:47:14 拓冰建站 浏览量
数据结构栈 1. 栈的基本概念1.1.1 概念栈Stack是一种限定仅在表的一端进行插入和删除操作的线性表。这一端称为栈顶top另一端称为栈底bottom。当栈中不包含任何元素时称为空栈。栈遵循后进先出LIFO, Last In First Out的原则最后入栈的元素最先被取出最先进栈的元素最后被取出。入栈Push将元素插入到栈顶的操作。出栈Pop从栈顶移除元素的操作。取栈顶元素Top获取栈顶元素但不删除它。这种特性使得栈在程序设计中具有广泛的应用场景如函数调用、表达式求值、括号匹配、递归实现等。1.1.2 入栈与出栈顺序分析以序列1, 2, 3为例若依次入栈则栈内状态变化如下操作栈状态从底到顶入栈1[1]入栈2[1, 2]入栈3[1, 2, 3]此时若连续出栈三次得到的结果为3, 2, 1—— 正好是逆序输出。⚠️ 关键点任意合法的入栈序列其对应的出栈序列必须满足“后进先出”的约束。例如无法通过合法操作使1, 3, 2成为出栈顺序因为 3 在 2 前面入栈却在 2 后面出栈违反了栈的 LIFO 特性。1.1.3 栈的基本操作接口定义以下是栈的核心接口函数原型适用于 C 语言实现// 栈的初始化voidStackInit(Stack*s);// 栈的销毁voidStackDestroy(Stack*s);// 元素入栈进栈voidStackPush(Stack*s,STDataType x);// 元素出栈并返回栈顶元素STDataTypeStackPop(Stack*s);// 获取栈顶元素不删除STDataTypeStackTop(Stack*s);// 获取栈中有效元素个数intStackSize(Stack*s);// 判断栈是否为空boolStackEmpty(Stack*s);这些接口构成了栈功能完整性的基础所有实现都围绕这组操作展开。1.2 栈的顺序存储结构栈的顺序存储基于数组实现分为静态顺序栈与动态顺序栈两种形式。1.2.1 静态顺序栈 vs 动态顺序栈类型特点适用场景静态顺序栈使用固定大小的数组空间不可变已知最大容量且不会超限的情况动态顺序栈使用malloc动态申请内存支持扩容大多数实际应用推荐使用❗ 静态顺序栈存在明显缺陷一旦数据量超过预设容量将导致溢出错误。因此在通用性要求高的系统中应优先采用动态顺序栈。1.2.2 动态顺序栈的实现核心代码typedefintSTDataType;// 可根据需要修改类型typedefstructStack{STDataType*data;// 存储栈元素的动态数组inttop;// 栈顶指针指向下一个可插入位置intcapacity;// 当前容量}Stack;// 初始化栈voidStackInit(Stack*s){assert(s!NULL);s-data(STDataType*)malloc(sizeof(STDataType)*4);// 初始容量为4if(s-dataNULL){printf(StackInit: 内存分配失败\n);exit(-1);}s-top0;s-capacity4;}// 销毁栈voidStackDestroy(Stack*s){assert(s!NULL);free(s-data);s-dataNULL;s-top0;s-capacity0;}// 扩容函数当栈满时自动扩展容量voidStackResize(Stack*s){assert(s!NULL);intnewCapacitys-capacity*2;STDataType*newData(STDataType*)realloc(s-data,sizeof(STDataType)*newCapacity);if(newDataNULL){printf(StackResize: 内存重分配失败\n);exit(-1);}s-datanewData;s-capacitynewCapacity;}// 入栈操作voidStackPush(Stack*s,STDataType x){assert(s!NULL);// 检查是否需要扩容if(s-tops-capacity){StackResize(s);}s-data[s-top]x;s-top;}// 出栈操作并返回栈顶元素STDataTypeStackPop(Stack*s){assert(s!NULL);assert(!StackEmpty(s));// 确保栈非空STDataType rets-data[--s-top];// 先减再取值returnret;}// 获取栈顶元素不删除STDataTypeStackTop(Stack*s){assert(s!NULL);assert(!StackEmpty(s));returns-data[s-top-1];}// 获取栈中元素个数intStackSize(Stack*s){assert(s!NULL);returns-top;}// 判断栈是否为空boolStackEmpty(Stack*s){assert(s!NULL);returns-top0;}✅关键优势时间复杂度所有操作均为O(1)O(1)O(1)除非触发扩容扩容策略采用加倍增长摊还分析表明平均每次插入成本仍为O(1)O(1)O(1)缓存友好连续内存布局提高 CPU 缓存命中率无内存碎片整个栈占用一块连续内存。1.3 栈的链式存储结构链式存储利用链表实现栈适合不确定元素数量或频繁动态增删的场景。1.3.1 链式栈结构设计要点推荐使用单链表实现必须将头节点作为栈顶即入栈为头插出栈为头删不建议使用带头结点的链表因为头插头删逻辑并无简化若使用双向链表虽可两端操作但额外开销大多一个指针性价比低。 结论单链表 不带头结点 头插头删 最优链式栈实现方案1.3.2 链式栈的结构体定义typedefintSTDataType;typedefstructListNode{STDataType data;structListNode*next;}LSNode;typedefstructLinkStack{LSNode*topHead;// 指向栈顶节点即链表头intsize;// 当前栈中元素个数}LinkStack;1.3.3 入栈操作头插法voidLinkStackPush(LinkStack*s,STDataType x){assert(s!NULL);LSNode*newNode(LSNode*)malloc(sizeof(LSNode));if(newNodeNULL){printf(LinkStackPush: 节点申请失败\n);exit(-1);}newNode-datax;newNode-nexts-topHead;// 新节点指向原栈顶s-topHeadnewNode;// 更新栈顶指针s-size;} 分析新元素插入至链表头部时间复杂度O(1)O(1)O(1)无需遍历。1.3.4 出栈操作头删法STDataTypeLinkStackPop(LinkStack*s){assert(s!NULL);assert(!LinkStackEmpty(s));LSNode*delNodes-topHead;STDataType topValdelNode-data;s-topHeaddelNode-next;// 移动栈顶指针free(delNode);// 释放旧节点s-size--;returntopVal;}✅ 优点出栈操作高效无需遍历缺点每个节点需额外内存开销指针域且不连续缓存性能差。1.4 栈的顺序存储与链式存储对比分析对比维度顺序存储动态数组链式存储单链表插入/删除时间复杂度O(1)O(1)O(1)均摊O(1)O(1)O(1)空间利用率高无额外指针开销低每个节点多一个指针内存连续性是连续存储否分散存储缓存命中率高局部性好低跳跃访问扩容代价一次性拷贝但频率低无实现复杂度中等较简单是否易发生内存碎片否是频繁申请释放推荐程度✅ 强烈推荐用于大多数场景仅在特定需求下使用结论在绝大多数情况下推荐使用动态顺序栈。其性能优越、内存效率高、缓存友好且实现清晰简洁。链式栈更适合教学演示或极端情况如无限增长且不允许扩容。1.5 实际应用示例括号匹配检测利用栈可以高效判断字符串中的括号是否匹配#includestdio.h#includeassert.hboolIsBracketMatch(constchar*str){Stack s;StackInit(s);for(inti0;str[i]!\0;i){charchstr[i];if(ch(||ch[||ch{){StackPush(s,ch);}elseif(ch)||ch]||ch}){if(StackEmpty(s)){StackDestroy(s);returnfalse;// 缺少左括号}chartopStackTop(s);if((ch)top!()||(ch]top![)||(ch}top!{)){StackDestroy(s);returnfalse;// 匹配失败}StackPop(s);}// 忽略其他字符}bool resultStackEmpty(s);StackDestroy(s);returnresult;}intmain(){constchar*test1(([]));constchar*test2([)];printf(%s - %s\n,test1,IsBracketMatch(test1)?匹配:不匹配);printf(%s - %s\n,test2,IsBracketMatch(test2)?匹配:不匹配);return0;}✅ 输出结果(([])) - 匹配 ([)] - 不匹配该例子充分体现了栈在解决嵌套结构验证问题上的强大能力。总结栈的核心价值与最佳实践核心思想后进先出LIFO限制操作位置提升效率。首选实现方式动态顺序栈兼顾性能与实用性。典型应用场景函数调用栈运行时堆栈表达式求值中缀转后缀括号匹配、路径回溯浏览器前进后退功能递归改写为迭代显式栈模拟学习建议掌握顺序栈的动态扩容机制理解栈在递归中的作用练习至少两个经典题目括号匹配、逆波兰表达式求值。✅ 本文内容已全面覆盖栈的基础理论、两种存储结构实现、性能对比及实战案例适合作为数据结构入门者的系统学习资料。