【数据结构与算法】第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>)。

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

相关推荐
灰子学技术2 小时前
C++ 代码质量检测工具集合技术文档
开发语言·c++
亓才孓2 小时前
【SQLAlchemy】个人快速学习笔记
笔记·学习
210Brian2 小时前
嘉立创EDA硬件设计与实战学习笔记(三):51单片机核心板原理图设计
笔记·学习·51单片机
散峰而望2 小时前
【数据结构】单调栈与单调队列深度解析:从模板到实战,一网打尽
开发语言·数据结构·c++·后端·算法·github·推荐算法
qwehjk20082 小时前
内存泄漏自动检测系统
开发语言·c++·算法
tankeven2 小时前
HJ153 实现字通配符*
c++·算法
草莓熊Lotso2 小时前
MySQL 多表连接查询实战:内连接 + 外连接
android·运维·数据库·c++·mysql
旖-旎3 小时前
位运算(两整数之和)(3)
c++·算法·leetcode·位运算
杨校3 小时前
杨校老师课堂备战C++之数据结构中栈结构专题训练
开发语言·数据结构·c++