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

数据结构小扫尾——栈

数据结构小扫尾——栈

@jarringslee

文章目录

  • 数据结构小扫尾——栈
    • 栈本质上是一种特殊的线性表。
      • (一)线性表的定义
      • (二)线性表的运算
    • 什么是栈。
      • (一)栈的定义
      • (二)栈的分类
      • (三)栈的存储结构
      • (四)栈的示例
    • 二、栈的实现
      • (一)顺序栈的实现
      • (二)链栈的实现
    • 栈的应用
      • (一)括号匹配
      • (二)中缀表达式转后缀表达式
    • 例题
      • [20. 有效的括号 - 力扣(LeetCode)](https://leetcode.cn/problems/valid-parentheses/)
      • [150. 逆波兰表达式求值 - 力扣(LeetCode)](https://leetcode.cn/problems/evaluate-reverse-polish-notation/)

​ 栈stack作为一种重要的数据结构,在实际开发中有着广泛的应用,如函数调用、表达式求值等。

栈本质上是一种特殊的线性表。

(一)线性表的定义

线性表是一种最基础的数据结构,它由具有相同特性的数据元素构成,这些数据元素之间存在着线性关系,即第一个元素称为表头,最后一个元素称为表尾,除第一个元素外,每个元素有且只有一个前驱,除最后一个元素外,每个元素有且只有一个后继。线性表可以顺序存储,也可以链式存储。

(二)线性表的运算

1. **插入** :在表中指定位置插入一个元素。
2. **删除** :删除表中指定位置的元素。
3. **查找** :查找表中满足特定条件的元素。

什么是栈。

(一)栈的定义

栈是一种特殊的线性表,它满足后进先出的特性,即最后被插入的元素最先被删除。栈的插入和删除操作都只能在栈顶进行。

就像一个桶。我们把东西按顺序放进去,最后一个就放在了桶的最顶端。我们需要拿出来的时候,先拿出来最后放进去的,最后拿出来第一个放进去的。起到了一个逆序的过程。

(二)栈的分类

  • 顺序栈 :使用数组实现的栈,元素在数组中连续存储。
  • 链栈 :使用链表实现的栈,元素通过指针链接在一起。

(三)栈的存储结构

栈通常使用顺序栈和链栈来存储。顺序栈的存储结构如下:

typedef int DataType;
typedef struct Stack {DataType* a; //存放栈元素的数组int top;     //栈顶指针int capacity; //栈的最大容量
} Stack;

链栈的存储结构如下:

typedef int DataType;
typedef struct Node {DataType data;struct Node* next;
} Node;typedef struct LinkStack {Node* top; //栈顶指针
} LinkStack;

(四)栈的示例

假设我们有一个顺序栈 [1, 2, 3, 4, 5],其中 5 是栈顶元素。当插入元素 6 时,栈变为 [1, 2, 3, 4, 5, 6]。当删除元素时,栈变为 [1, 2, 3, 4, 5]。

二、栈的实现

(一)顺序栈的实现

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>// 定义栈中存储的数据类型为整型
typedef int DataType;// 定义顺序栈的结构体,包含存储元素的数组、栈顶指针和栈的最大容量
typedef struct Stack {DataType* a; // 存放栈元素的数组int top;     // 栈顶指针,初始值为 -1 表示栈为空int capacity; // 栈的最大容量
} Stack;// 栈的初始化函数
void StackInit(Stack* pstack) {assert(pstack); // 断言确保传入的指针非空pstack->a = (DataType*)malloc(sizeof(DataType) * 4); // 分配初始大小为 4 的动态数组if (pstack->a == NULL) { // 判断内存分配是否成功perror("malloc fail"); // 若分配失败,输出错误信息return;}pstack->top = -1; // 栈顶指针初始化为 -1pstack->capacity = 4; // 栈的初始容量设为 4
}// 栈的销毁函数
void StackDestroy(Stack* pstack) {assert(pstack); // 断言确保传入的指针非空free(pstack->a); // 释放动态分配的数组内存pstack->a = NULL; // 将数组指针置为空,避免野指针pstack->top = -1; // 栈顶指针重置为 -1pstack->capacity = 0; // 栈的容量重置为 0
}// 栈的插入(压栈)操作
void StackPush(Stack* pstack, DataType x) {assert(pstack); // 断言确保传入的指针非空// 判断栈是否已满,若栈满则扩容if (pstack->top == pstack->capacity - 1) {DataType* tmp = (DataType*)realloc(pstack->a, sizeof(DataType) * pstack->capacity * 2);if (tmp == NULL) { // 判断内存重新分配是否成功perror("realloc fail"); // 若扩容失败,输出错误信息return;}pstack->a = tmp; // 更新数组指针pstack->capacity *= 2; // 栈的容量翻倍}pstack->a[++pstack->top] = x; // 栈顶指针增 1 后存入新元素
}// 栈的删除(弹栈)操作
void StackPop(Stack* pstack) {assert(pstack); // 断言确保传入的指针非空assert(!StackEmpty(pstack)); // 断言确保栈不为空,防止溢出pstack->top--; // 栈顶指针减 1,实现元素出栈
}// 判断栈是否为空的函数
bool StackEmpty(Stack* pstack) {assert(pstack); // 断言确保传入的指针非空return pstack->top == -1; // 若栈顶指针为 -1 则栈为空,返回 true,否则返回 false
}// 获取栈顶元素的函数
DataType StackTop(Stack* pstack) {assert(pstack); // 断言确保传入的指针非空assert(!StackEmpty(pstack)); // 断言确保栈不为空,防止访问越界return pstack->a[pstack->top]; // 返回栈顶元素的值
}// 获取栈中元素个数的函数
int StackSize(Stack* pstack) {assert(pstack); // 断言确保传入的指针非空return pstack->top + 1; // 栈顶指针加 1 即为栈中元素个数
}

(二)链栈的实现

// 定义栈中存储的数据类型为整型
typedef int DataType;// 定义链栈节点的结构体,包含存储的数据和指向下一个节点的指针
typedef struct Node {DataType data; // 节点存储的数据struct Node* next; // 指向下一个节点的指针
} Node;// 定义链栈的结构体,包含指向栈顶的指针
typedef struct LinkStack {Node* top; // 栈顶指针,初始值为 NULL 表示栈为空
} LinkStack;// 链栈的初始化函数
void LinkStackInit(LinkStack* pstack) {assert(pstack); // 断言确保传入的指针非空pstack->top = NULL; // 栈顶指针初始化为空
}// 链栈的销毁函数
void LinkStackDestroy(LinkStack* pstack) {assert(pstack); // 断言确保传入的指针非空Node* cur = pstack->top; // 从栈顶开始遍历while (cur) { // 遍历链栈中的每个节点Node* next = cur->next; // 保存下一个节点的地址free(cur); // 释放当前节点的内存cur = next; // 移动到下一个节点}pstack->top = NULL; // 销毁后栈顶指针置为空
}// 链栈的插入(压栈)操作
void LinkStackPush(LinkStack* pstack, DataType x) {assert(pstack); // 断言确保传入的指针非空Node* new_node = (Node*)malloc(sizeof(Node)); // 动态分配新节点内存if (new_node == NULL) { // 判断内存分配是否成功perror("malloc fail"); // 若分配失败,输出错误信息return;}new_node->data = x; // 将数据存入新节点new_node->next = pstack->top; // 新节点的 next 指针指向原栈顶pstack->top = new_node; // 更新栈顶指针为新节点
}// 链栈的删除(弹栈)操作
void LinkStackPop(LinkStack* pstack) {assert(pstack); // 断言确保传入的指针非空assert(!LinkStackEmpty(pstack)); // 断言确保栈不为空,防止溢出Node* tmp = pstack->top; // 保存当前栈顶节点pstack->top = pstack->top->next; // 更新栈顶指针为下一个节点free(tmp); // 释放原栈顶节点内存
}// 判断链栈是否为空的函数
bool LinkStackEmpty(LinkStack* pstack) {assert(pstack); // 断言确保传入的指针非空return pstack->top == NULL; // 若栈顶指针为空则栈为空,返回 true,否则返回 false
}// 获取链栈栈顶元素的函数
DataType LinkStackTop(LinkStack* pstack) {assert(pstack); // 断言确保传入的指针非空assert(!LinkStackEmpty(pstack)); // 断言确保栈不为空,防止访问越界return pstack->top->data; // 返回栈顶元素的值
}// 获取链栈中元素个数的函数
int LinkStackSize(LinkStack* pstack) {assert(pstack); // 断言确保传入的指针非空int size = 0; // 初始化计数器为 0Node* cur = pstack->top; // 从栈顶开始遍历while (cur) { // 遍历链栈中的每个节点size++; // 每遍历一个节点计数器加 1cur = cur->next; // 移动到下一个节点}return size; // 返回链栈中元素的总个数
}

栈的应用

(一)括号匹配

括号匹配是一种经典的栈应用,用于检查表达式中的括号是否正确匹配。

// 判断括号匹配的函数
bool isValid(char* s) {Stack stack; // 定义一个顺序栈StackInit(&stack); // 初始化栈for (int i = 0; s[i] != '\0'; i++) { // 遍历字符串中的每个字符if (s[i] == '(' || s[i] == '[' || s[i] == '{') { // 若字符为左括号StackPush(&stack, s[i]); // 将左括号压入栈中} else { // 若字符为右括号if (StackEmpty(&stack)) { // 判断栈是否为空,若为空说明没有匹配的左括号StackDestroy(&stack); // 销毁栈以释放资源return false; // 返回不匹配的结果}char top = StackTop(&stack); // 取出栈顶元素(最近的左括号)StackPop(&stack); // 将栈顶元素弹出// 判断右括号与左括号是否匹配if ((s[i] == ')' && top != '(') || (s[i] == ']' && top != '[') || (s[i] == '}' && top != '{')) {StackDestroy(&stack); // 销毁栈以释放资源return false; // 若不匹配,返回不匹配的结果}}}bool result = StackEmpty(&stack); // 若遍历结束栈为空,则所有括号匹配;否则存在未匹配的左括号StackDestroy(&stack); // 销毁栈以释放资源return result; // 返回判断结果
}

(二)中缀表达式转后缀表达式

中缀表达式转后缀表达式是另一种经典的栈应用。

// 判断字符是否为运算符的辅助函数
bool isOperator(char c) {return c == '+' || c == '-' || c == '*' || c == '/'; // 若字符是其中之一,则返回 true 表示是运算符
}// 获取运算符优先级的辅助函数
int precedence(char op) {if (op == '+' || op == '-') { // 加减运算符优先级较低,返回 1return 1;} else if (op == '*' || op == '/') { // 乘除运算符优先级较高,返回 2return 2;} else { // 其他字符不是运算符,返回 0return 0;}
}// 中缀表达式转后缀表达式的函数
void infixToPostfix(char* infix, char* postfix) {Stack stack; // 定义一个顺序栈StackInit(&stack); // 初始化栈int j = 0; // 用于记录后缀表达式中字符的位置for (int i = 0; infix[i] != '\0'; i++) { // 遍历中缀表达式的每个字符if (infix[i] == '(') { // 若遇到左括号,直接压栈StackPush(&stack, infix[i]);} else if (infix[i] == ')') { // 若遇到右括号// 依次弹出栈中的运算符直到遇到左括号,并将弹出的运算符加入后缀表达式while (!StackEmpty(&stack) && StackTop(&stack) != '(') {postfix[j++] = StackTop(&stack);StackPop(&stack);}StackPop(&stack); // 弹出左括号,但不加入后缀表达式} else if (isOperator(infix[i])) { // 若遇到运算符// 将栈中优先级大于等于当前运算符的运算符依次弹出并加入后缀表达式while (!StackEmpty(&stack) && precedence(infix[i]) <= precedence(StackTop(&stack))) {postfix[j++] = StackTop(&stack);StackPop(&stack);}StackPush(&stack, infix[i]); // 将当前运算符压栈} else { // 若是操作数,直接加入后缀表达式postfix[j++] = infix[i];}}// 弹出栈中剩余的运算符并加入后缀表达式while (!StackEmpty(&stack)) {postfix[j++] = StackTop(&stack);StackPop(&stack);}postfix[j] = '\0'; // 在后缀表达式末尾添加字符串结束标志StackDestroy(&stack); // 销毁栈以释放资源
}

例题

20. 有效的括号 - 力扣(LeetCode)

给定一个只包含三种括号字符 '('')''{''}''['']' 的字符串 s,判断 s 是否是有效的括号字符串。

示例 1:

输入:s = "()"
输出:true

示例 2:

输入:s = "()[]{}"
输出:true

示例 3:

输入:s = "(]"
输出:false

​ 我们首先初始化一个空栈,然后遍历字符串中的每一个字符。如果字符串是左括号,则压入栈。如果字符是右括号,则检查栈顶元素是否匹配,匹配则弹栈,否则返回 false。如果字符是右括号,则检查栈顶元素是否匹配,匹配则弹栈,否则返回 false

// 判断括号匹配的函数(与前面的 isValid 函数一致)
bool isValid(char* s) {Stack stack;StackInit(&stack);for (int i = 0; s[i] != '\0'; i++) {if (s[i] == '(' || s[i] == '[' || s[i] == '{') {StackPush(&stack, s[i]);} else {if (StackEmpty(&stack)) {StackDestroy(&stack);return false;}char top = StackTop(&stack);StackPop(&stack);if ((s[i] == ')' && top != '(') || (s[i] == ']' && top != '[') || (s[i] == '}' && top != '{')) {StackDestroy(&stack);return false;}}}bool result = StackEmpty(&stack);StackDestroy(&stack);return result;
}

150. 逆波兰表达式求值 - 力扣(LeetCode)

根据逆波兰表示法,求表达式的值。

有效的算符包括 +-*/。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

示例 1:

输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀逆波兰表达式为:((2 + 1) * 3) = 9

示例 2:

输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀逆波兰表达式为:(4 + (13 / 5)) = 6

​ 依旧是初始化一个空栈,然后遍历逆波兰表达式。如果元素是数字,则压入栈。如果元素是运算符,则弹出栈顶的两个数字进行运算,将结果压入栈。最后栈顶元素即为结果。

// 逆波兰表达式求值的函数
int evalRPN(char** tokens, int tokensSize) {Stack stack;StackInit(&stack);for (int i = 0; i < tokensSize; i++) { // 遍历逆波兰表达式的每个标记char* token = tokens[i];// 判断是否为运算符if (strcmp(token, "+") == 0) {assert(!StackEmpty(&stack)); // 确保栈中有足够的操作数int b = StackTop(&stack); // 取出栈顶元素作为第二个操作数StackPop(&stack);int a = StackTop(&stack); // 取出下一个栈顶元素作为第一个操作数StackPop(&stack);StackPush(&stack, a + b); // 将运算结果压栈} else if (strcmp(token, "-") == 0) {assert(!StackEmpty(&stack));int b = StackTop(&stack);StackPop(&stack);int a = StackTop(&stack);StackPop(&stack);StackPush(&stack, a - b);} else if (strcmp(token, "*") == 0) {assert(!StackEmpty(&stack));int b = StackTop(&stack);StackPop(&stack);int a = StackTop(&stack);StackPop(&stack);StackPush(&stack, a * b);} else if (strcmp(token, "/") == 0) {assert(!StackEmpty(&stack));int b = StackTop(&stack);StackPop(&stack);int a = StackTop(&stack);StackPop(&stack);StackPush(&stack, a / b);} else { // 若是操作数,则转换为整型后压栈StackPush(&stack, atoi(token));}}int result = StackTop(&stack); // 最终结果位于栈顶StackDestroy(&stack);return result;
}

相关文章:

  • JAVA:使用 Maven Assembly 创建自定义打包的技术指南
  • Kubernetes(k8s)学习笔记(七)--KubeSphere 最小化安装
  • 音频感知动画新纪元:Sonic让你的作品更生动
  • 矩阵置零(中等)
  • 五一假期集训【补题】
  • 研0大模型学习(第12天)
  • 【C++】智能指针RALL实现shared_ptr
  • android-ndk开发(1): 搭建环境
  • 基于SpringBoot的漫画网站设计与实现
  • flink rocksdb状态说明
  • 组合两个表 --- MySQL [Leetcode 题目详解]
  • JavaScript篇:“解密JavaScript对象的诞生之旅:从new操作符到实例化全过程“
  • 使用注意力机制的seq2seq
  • 【SaaS多租架构】数据隔离与性能平衡
  • 【2025最新】AI绘画终极提示词库|MidjourneyStable Diffusion通用公式大全
  • Cisco Packet Tracer 选项卡的使用
  • 【神经网络与深度学习】普通自编码器和变分自编码器的区别
  • JavaScript 实现输入框的撤销功能
  • Spring Boot多模块划分设计
  • # 机器学习实操 第二部分 神经网络和深度学习 第12章 自定义模型和训练循环
  • 贵州黔西市游船倾覆事故发生后,多家保险公司紧急响应
  • 金正恩视察重要坦克厂并强调更迭陆军装备
  • 李强签署国务院令,公布修订后的《中华人民共和国植物新品种保护条例》
  • 应急管理部派出工作组赴山西太原小区爆炸现场指导救援处置
  • 日菲同意扩大安全合作,外交部:反对任何在本地区拉帮结派的做法
  • 兴业银行一季度净赚超237亿降逾2%,营收降逾3%