当前位置: 首页 > news >正文

【数据结构与算法】第12篇:栈(二):链式栈与括号匹配问题

一、链式栈的实现

1.1 链式栈的结构

链式栈用链表存储数据,栈顶就是链表的头结点。因为单链表的头插和头删都是O(1),非常适合做栈。

text

栈顶 → [data|next] → [data|next] → [data|next] → NULL

节点结构

c

typedef struct StackNode { int data; // 数据域 struct StackNode *next; // 指针域 } StackNode, *PStackNode; typedef struct { PStackNode top; // 栈顶指针 int size; // 栈中元素个数 } LinkStack;

1.2 初始化

c

void initStack(LinkStack *stack) { stack->top = NULL; stack->size = 0; }

1.3 判断空栈

c

int isEmpty(LinkStack *stack) { return stack->top == NULL; }

1.4 入栈(push)

入栈就是在链表头部插入节点。

c

int push(LinkStack *stack, int value) { PStackNode newNode = (PStackNode)malloc(sizeof(StackNode)); if (newNode == NULL) { printf("内存分配失败\n"); return -1; } newNode->data = value; newNode->next = stack->top; stack->top = newNode; stack->size++; return 0; }

1.5 出栈(pop)

出栈就是删除链表头节点。

c

int pop(LinkStack *stack, int *value) { if (isEmpty(stack)) { printf("栈为空,无法出栈\n"); return -1; } PStackNode temp = stack->top; *value = temp->data; stack->top = temp->next; free(temp); stack->size--; return 0; }

1.6 获取栈顶元素

c

int getTop(LinkStack *stack, int *value) { if (isEmpty(stack)) { printf("栈为空\n"); return -1; } *value = stack->top->data; return 0; }

1.7 获取栈的大小

c

int getSize(LinkStack *stack) { return stack->size; }

1.8 销毁栈

c

void destroyStack(LinkStack *stack) { PStackNode cur = stack->top; while (cur != NULL) { PStackNode temp = cur; cur = cur->next; free(temp); } stack->top = NULL; stack->size = 0; }

二、链式栈完整代码

c

#include <stdio.h> #include <stdlib.h> typedef struct StackNode { int data; struct StackNode *next; } StackNode, *PStackNode; typedef struct { PStackNode top; int size; } LinkStack; void initStack(LinkStack *stack) { stack->top = NULL; stack->size = 0; } int isEmpty(LinkStack *stack) { return stack->top == NULL; } int push(LinkStack *stack, int value) { PStackNode newNode = (PStackNode)malloc(sizeof(StackNode)); if (newNode == NULL) { printf("内存分配失败\n"); return -1; } newNode->data = value; newNode->next = stack->top; stack->top = newNode; stack->size++; return 0; } int pop(LinkStack *stack, int *value) { if (isEmpty(stack)) { printf("栈为空,无法出栈\n"); return -1; } PStackNode temp = stack->top; *value = temp->data; stack->top = temp->next; free(temp); stack->size--; return 0; } int getTop(LinkStack *stack, int *value) { if (isEmpty(stack)) { printf("栈为空\n"); return -1; } *value = stack->top->data; return 0; } int getSize(LinkStack *stack) { return stack->size; } void destroyStack(LinkStack *stack) { PStackNode cur = stack->top; while (cur != NULL) { PStackNode temp = cur; cur = cur->next; free(temp); } stack->top = NULL; stack->size = 0; } void printStack(LinkStack *stack) { printf("栈顶 → "); PStackNode cur = stack->top; while (cur != NULL) { printf("[%d] ", cur->data); if (cur->next != NULL) printf("\n "); cur = cur->next; } printf("\n栈底\n"); } int main() { LinkStack stack; initStack(&stack); push(&stack, 10); push(&stack, 20); push(&stack, 30); printStack(&stack); printf("栈大小: %d\n", getSize(&stack)); int val; pop(&stack, &val); printf("出栈: %d\n", val); pop(&stack, &val); printf("出栈: %d\n", val); printStack(&stack); printf("栈大小: %d\n", getSize(&stack)); destroyStack(&stack); return 0; }

运行结果:

text

栈顶 → [30] [20] [10] 栈底 栈大小: 3 出栈: 30 出栈: 20 栈顶 → [10] 栈底 栈大小: 1

三、经典应用:括号匹配

3.1 问题描述

给定一个字符串,只包含()[]{}三种括号,判断括号是否匹配。

匹配规则

  • 左括号必须用相同类型的右括号闭合

  • 左括号必须以正确的顺序闭合

  • 嵌套时,内层的括号必须先闭合

示例

  • "()"→ 匹配

  • "()[]{}"→ 匹配

  • "{[]}"→ 匹配

  • "([)]"→ 不匹配(顺序错误)

  • "((()"→ 不匹配(数量不对)

3.2 算法思路

用栈来实现:

  1. 遍历字符串的每个字符

  2. 如果是左括号([{,则入栈

  3. 如果是右括号)]}

    • 如果栈为空,说明没有对应的左括号,返回不匹配

    • 如果栈顶元素不是对应的左括号,返回不匹配

    • 如果匹配,则栈顶元素出栈

  4. 遍历结束后,如果栈为空则匹配,否则不匹配

画个图理解"{[]}"

text

遍历 '{' : 栈 → [ { ] 遍历 '[' : 栈 → [ { [ ] 遍历 ']' : 栈顶是'[',匹配,弹出 → 栈 → [ { ] 遍历 '}' : 栈顶是'{',匹配,弹出 → 栈 → [ ] 结束,栈为空 → 匹配成功

3.3 代码实现

c

#include <stdio.h> #include <stdlib.h> #include <string.h> // 链式栈节点(存储字符) typedef struct CharNode { char data; struct CharNode *next; } CharNode, *PCharNode; typedef struct { PCharNode top; int size; } CharStack; void initCharStack(CharStack *stack) { stack->top = NULL; stack->size = 0; } int isCharStackEmpty(CharStack *stack) { return stack->top == NULL; } int charPush(CharStack *stack, char value) { PCharNode newNode = (PCharNode)malloc(sizeof(CharNode)); if (newNode == NULL) return -1; newNode->data = value; newNode->next = stack->top; stack->top = newNode; stack->size++; return 0; } int charPop(CharStack *stack, char *value) { if (isCharStackEmpty(stack)) return -1; PCharNode temp = stack->top; *value = temp->data; stack->top = temp->next; free(temp); stack->size--; return 0; } int charGetTop(CharStack *stack, char *value) { if (isCharStackEmpty(stack)) return -1; *value = stack->top->data; return 0; } void destroyCharStack(CharStack *stack) { PCharNode cur = stack->top; while (cur != NULL) { PCharNode temp = cur; cur = cur->next; free(temp); } stack->top = NULL; stack->size = 0; } // 判断两个括号是否匹配 int isMatch(char left, char right) { return (left == '(' && right == ')') || (left == '[' && right == ']') || (left == '{' && right == '}'); } // 括号匹配函数 int bracketMatch(const char *str) { CharStack stack; initCharStack(&stack); for (int i = 0; str[i] != '\0'; i++) {
char ch = str[i]; // 左括号入栈 if (ch == '(' || ch == '[' || ch == '{') { charPush(&stack, ch); } // 右括号处理 else if (ch == ')' || ch == ']' || ch == '}') { // 栈为空,没有匹配的左括号 if (isCharStackEmpty(&stack)) { destroyCharStack(&stack); return 0; } char topChar; charGetTop(&stack, &topChar); // 不匹配 if (!isMatch(topChar, ch)) { destroyCharStack(&stack); return 0; } // 匹配,弹出栈顶 charPop(&stack, &topChar); } // 其他字符忽略(如字母、数字) } // 栈空则完全匹配 int result = isCharStackEmpty(&stack); destroyCharStack(&stack); return result; } int main() { char test1[] = "()"; char test2[] = "()[]{}"; char test3[] = "{[()]}"; char test4[] = "([)]"; char test5[] = "((()"; char test6[] = "a+b*(c-d)/{e+f}"; printf("\"%s\" : %s\n", test1, bracketMatch(test1) ? "匹配" : "不匹配"); printf("\"%s\" : %s\n", test2, bracketMatch(test2) ? "匹配" : "不匹配"); printf("\"%s\" : %s\n", test3, bracketMatch(test3) ? "匹配" : "不匹配"); printf("\"%s\" : %s\n", test4, bracketMatch(test4) ? "匹配" : "不匹配"); printf("\"%s\" : %s\n", test5, bracketMatch(test5) ? "匹配" : "不匹配"); printf("\"%s\" : %s\n", test6, bracketMatch(test6) ? "匹配" : "不匹配"); return 0; }

运行结果:

text

"()" : 匹配 "()[]{}" : 匹配 "{[()]}" : 匹配 "([)]" : 不匹配 "((()" : 不匹配 "a+b*(c-d)/{e+f}" : 匹配

四、顺序栈 vs 链式栈

对比项顺序栈链式栈
存储方式数组(连续内存)链表(非连续)
容量固定,可能溢出动态,受内存限制
入栈O(1)O(1)
出栈O(1)O(1)
内存利用率可能浪费每节点多一个指针
适用场景大小已知大小未知,动态变化

选择建议

  • 如果栈的大小可以预估,用顺序栈更简单高效

  • 如果栈的大小不确定,用链式栈更安全


五、栈的其他应用

括号匹配是栈的经典应用,栈还常用于:

应用说明
表达式求值中缀转后缀,计算表达式
函数调用栈递归函数的底层实现
撤销操作Ctrl+Z 撤销
浏览器后退记录浏览历史
深度优先搜索图的DFS算法

六、小结

这一篇我们实现了链式栈,并用它解决了括号匹配问题:

内容要点
链式栈用单链表实现,头插头删,没有容量限制
入栈O(1),在链表头部插入
出栈O(1),删除链表头部
括号匹配左括号入栈,右括号与栈顶匹配
时间复杂度O(n),遍历一次字符串

括号匹配的核心思想

  • 遇到左括号就压栈

  • 遇到右括号就弹栈比较

  • 栈空且遍历完才算匹配

下一篇我们会讲栈的另一个经典应用:表达式求值(中缀转后缀并计算)。


七、思考题

  1. 链式栈的入栈和出栈为什么选择在头部操作?如果在尾部操作会怎样?

  2. 括号匹配算法中,如果表达式中包含其他字符(如字母、数字),为什么可以直接忽略?

  3. 下面这个字符串能匹配吗?为什么?"({[}])"

  4. 尝试实现一个函数,判断HTML标签是否匹配(如<div><p></p></div>)。

欢迎在评论区讨论你的答案。

http://www.jsqmd.com/news/553761/

相关文章:

  • YOLO26官方镜像深度体验:推理、训练、下载一站式教程
  • DAMOYOLO-S实战案例:纺织品瑕疵检测(结合裁剪+局部放大)
  • 基于A*、遗传、蚁群优化和元胞自动机四种经典算法实现四种场景下六边形网格路径规划研究(Python代码实现)
  • StructBERT情感分类模型部署案例:高校科研项目中文社会情绪追踪系统
  • Comsol模拟多道激光熔覆热流耦合模型和教学教程,用到的物理场为流体传热层流以及动网格
  • 5分钟掌握QuickRecorder:高效屏幕录制的macOS实用指南
  • Qwen3-TTS开源镜像实操:与LangChain集成构建多语种AI Agent语音接口
  • 3步搞定Windows启动画面:HackBGRT让UEFI启动界面焕然一新
  • 通义千问1.5-1.8B-Chat-GPTQ-Int4环境部署详解:Anaconda虚拟环境配置
  • 拯救低清视频:AI视频增强技术全攻略
  • 昇腾NPU实战:PyTorch模型迁移与Ascend PyTorch Profiler深度调优
  • 3步解决显卡驱动残留问题:驱动清理工具DDU完全指南
  • 5个行业颠覆场景:用PptxGenJS实现办公自动化效率革命
  • 京东e卡怎么回收?这里有高价兑换的线上平台 - 团团收购物卡回收
  • 5步掌控Windows驱动仓库:DriverStore Explorer全方位优化指南
  • 科研开发神器:Miniconda-Python3.8镜像实测,轻松复现实验结果
  • Comsol三维激光切割:热流耦合模型与物理场解析
  • Ostrakon-VL-8B盲测挑战:与人类在图像描述任务上的对比
  • 哪里回收京东e卡?推荐可靠的线上兑换平台 - 团团收购物卡回收
  • Live2D资源解析技术解析与实战:从格式障碍到跨领域应用
  • OpenClaw知识库集成:Qwen3-VL:30B对接飞书Wiki作为外部记忆
  • 造相-Z-Image-Turbo 结合JavaScript动态网页:打造浏览器端实时AI绘图演示
  • ## 38|Python 分布式 ID 与雪花算法:高并发订单号设计
  • CTFhub实战:病毒文件解密、modbus协议解析与注册表取证
  • 京东e卡回收线上平台:快速、安全的兑换新选择 - 团团收购物卡回收
  • Facefusion小白避坑指南:轻松解决人脸检测失败的常见问题
  • Janus-Pro-7B赋能前端开发:基于Vue.js的智能代码助手实现
  • Phi-3-mini-128k-instruct部署教程:基于vLLM的GPU显存优化方案(A10/A100实测)
  • Docker与OpenSIPS 3.1:解决NAT问题的两种高效方案
  • AI 落地应用领域深度报告