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

数据结构 -- 栈

栈的定义与特性

栈是一种受限线性表,仅允许在表的一端(栈顶)进行插入(入栈)、删除(出栈)操作,遵循 “先进后出(LIFO,Last In First Out)” 原则。

栈的核心结构

  • 栈顶(Top):允许进行插入(入栈)、删除(出栈)操作的一端,动态变化(随元素增减移动 )。
  • 栈底(Bottom):不允许直接操作(插入 / 删除)的一端,位置相对固定(栈初始化时确定 )。

栈的典型操作

  • 入栈(Push):在栈顶添加新元素,栈顶指针上移。
  • 出栈(Pop):移除并返回栈顶元素,栈顶指针下移(栈非空时操作 )。
  • 获取栈顶元素(GetTop/Peek):返回栈顶元素值,但不删除该元素(栈非空时操作 )。
  • 判空(IsEmpty):检查栈是否无元素,辅助判断操作合法性(如避免空栈出栈报错 )。

栈的应用场景

依托 “先进后出” 特性,栈在编程中解决多种问题:

  • 递归实现:编译器用栈保存递归调用的上下文(参数、局部变量等 ),递归返回时按栈序恢复。
  • 表达式求值:如计算 “3 + (2 * 5)”,用栈暂存操作数、运算符,按优先级和括号匹配处理。
  • 括号匹配:遍历代码 / 字符串,左括号入栈、右括号尝试匹配栈顶左括号,最终栈空则匹配成功。
  • 浏览器历史记录:前进 / 后退功能,用栈保存访问页面,回退时按栈序弹出历史页。

栈的实现方式通常有 顺序栈(数组基实现) 和 链栈(链表基实现)

栈的操作函数

#include "LinkStack.h"   // 包含链栈的头文件,定义了LinkStack、LinkStackNode、DATATYPE等结构体
#include <stdio.h>       // 标准输入输出库,用于错误信息打印
#include <stdlib.h>      // 标准库,提供malloc、free等内存管理函数
#include <string.h>      // 字符串处理库,提供memcpy等内存复制函数/*** @brief 创建一个空的链栈* * @return LinkStack* 成功返回链栈指针,失败返回NULL*/
LinkStack* CreateLinkStack()
{// 为链栈结构体分配内存LinkStack* ls = malloc(sizeof(LinkStack));if (NULL == ls)  // 内存分配失败{perror("CreateLinkStack malloc error\n");  // 打印错误信息return NULL;}// 初始化栈顶指针为NULL(空栈)ls->top = NULL;// 初始化栈中元素个数为0ls->clen = 0;return ls;  // 返回创建的空栈
}/*** @brief 入栈操作,在栈顶增加元素(底层使用链表头插法实现)** @param ls 待操作的链栈指针* @param data 需要入栈的元素指针* @return int 0表示成功,非0表示失败*/
int PushLinkStack(LinkStack* ls, DATATYPE* data)
{// 为新节点分配内存LinkStackNode* newnode = malloc(sizeof(LinkStackNode));if (NULL == newnode)  // 内存分配失败{perror("PushLinkStack malloc error\n");  // 打印错误信息return 1;  // 返回失败状态}// 将待入栈的数据复制到新节点中(深拷贝,避免外部数据修改影响栈内数据)memcpy(&newnode->data, data, sizeof(DATATYPE));newnode->next = NULL;  // 新节点的next指针初始化为NULL// 头插法:新节点的next指向当前栈顶节点newnode->next = ls->top;// 更新栈顶指针为新节点(新节点成为新的栈顶)ls->top = newnode;// 栈中元素个数加1ls->clen++;return 0;  // 入栈成功
}/*** @brief 出栈操作,删除栈顶元素(底层使用链表头删法实现)** @param ls 待操作的链栈指针* @return int 0表示成功,非0表示失败*/
int PopLinkStack(LinkStack* ls)
{// 检查栈是否为空,为空则无法出栈if (IsEmptyLinkStack(ls)){printf("linkstack is empty\n");  // 打印错误信息return 1;  // 返回失败状态}// 保存当前栈顶节点的地址(避免释放后丢失指针)LinkStackNode* tmp = ls->top;// 更新栈顶指针为下一个节点(原栈顶的下一个节点成为新栈顶)ls->top = ls->top->next;// 释放原栈顶节点的内存free(tmp);// 栈中元素个数减1ls->clen--;return 0;  // 出栈成功
}/*** @brief 获取栈顶元素(不删除元素)** @param ls 待操作的链栈指针* @return DATATYPE* 成功返回栈顶元素的指针,失败返回NULL*/
DATATYPE* GetTopLinkStack(LinkStack* ls)
{// 检查栈是否为空,为空则无栈顶元素if (IsEmptyLinkStack(ls)){return NULL;  // 返回NULL表示失败}// 返回栈顶节点中数据的地址return &ls->top->data;
}/*** @brief 判断栈是否为空** @param ls 待操作的链栈指针* @return int 1表示栈为空,0表示栈不为空*/
int IsEmptyLinkStack(LinkStack* ls)
{// 通过栈中元素个数是否为0判断栈是否为空return 0 == ls->clen;
}/*** @brief 获取栈中元素的个数** @param ls 待操作的链栈指针* @return int 栈中元素的个数*/
int GetSizeLinkStack(LinkStack* ls)
{// 直接返回栈的长度属性return ls->clen;
}/*** @brief 销毁链栈,释放所有分配的内存** @param ls 待销毁的链栈指针* @return int 0表示成功*/
int DestroyLinkStack(LinkStack* ls)
{// 循环出栈,直到栈为空while (!IsEmptyLinkStack(ls)){PopLinkStack(ls);  // 出栈操作会释放每个节点的内存,并减少元素计数}// 释放链栈结构体本身的内存free(ls);return 0;  // 销毁成功
}

练习

1、括号匹配检查程序,用于检测 C 语言源文件中圆括号()、方括号[]、花括号{}的匹配情况。程序通过 "栈" 这种数据结构实现括号匹配检查,能定位并输出不匹配括号的位置(行号和列号),帮助排查代码中的语法错误。

#include <stdio.h>    // 标准输入输出库,用于文件操作和打印
#include <string.h>   // 字符串处理库,用于内存初始化等操作
#include "LinkStack.h"// 自定义链表栈头文件,提供栈的相关操作/*** 检查一行字符中的括号匹配情况* @param linebuf 待检查的行缓冲区* @param ls 栈指针,用于存储左括号信息* @param num 当前行的行号* @return 0表示当前行检查正常,1表示发现不匹配错误*/
int do_check(char* linebuf, LinkStack* ls, int num)
{char* tmp = linebuf;       // 临时指针,用于遍历行缓冲区中的每个字符DATATYPE data = {0};       // 存储括号信息的结构体(包含括号字符、行号、列号)DATATYPE* top = NULL;      // 指向栈顶元素的指针,用于检查匹配关系int col = 1;               // 记录当前字符在该行中的列号(从1开始计数)// 遍历行缓冲区中的每个字符,直到遇到字符串结束符'\0'while (*tmp){bzero(&data, sizeof(data));  // 清空data结构体,避免残留数据影响后续使用// 根据当前字符类型进行不同处理switch (*tmp){// 遇到左括号:入栈保存(记录括号类型和位置)case '(':case '[':case '{':data.c = *tmp;       // 存储当前左括号字符data.col = col;      // 记录当前列号data.row = num;      // 记录当前行号PushLinkStack(ls, &data);  // 将左括号信息压入栈break;// 遇到右圆括号')':检查栈顶是否为对应的左圆括号'('case ')':top = GetTopLinkStack(ls);  // 获取栈顶元素(最近的左括号)if (NULL == top)  // 栈为空:没有对应的左括号,右括号多余{printf("当前符号错误 %c 行:%d 列:%d\n", *tmp, num, col);return 1;  // 返回错误状态}// 栈顶是对应的左括号'(':弹出栈顶(匹配成功)if (top->c == '('){PopLinkStack(ls);}else  // 栈顶不是对应的左括号:括号类型不匹配{printf("当前符号错误 %c 行:%d 列:%d 或 栈顶符号 %c 行:%d 列:%d\n",*tmp, num, col, top->c, top->row, top->col);return 1;  // 返回错误状态}break;// 遇到右方括号']':检查栈顶是否为对应的左方括号'['case ']':top = GetTopLinkStack(ls);  // 获取栈顶元素if (NULL == top)  // 栈为空:右括号多余{printf("当前符号错误 %c 行:%d 列:%d\n", *tmp, num, col);return 1;}// 栈顶是对应的左方括号'[':弹出栈顶(匹配成功)if (top->c == '['){PopLinkStack(ls);}else  // 类型不匹配{printf("当前符号错误 %c 行:%d 列:%d 或 栈顶符号 %c 行:%d 列:%d\n",*tmp, num, col, top->c, top->row, top->col);return 1;}break;// 遇到右花括号'}':检查栈顶是否为对应的左花括号'{'case '}':top = GetTopLinkStack(ls);  // 获取栈顶元素if (NULL == top)  // 栈为空:右括号多余{printf("当前符号错误 %c 行:%d 列:%d\n", *tmp, num, col);return 1;}// 栈顶是对应的左花括号'{':弹出栈顶(匹配成功)if (top->c == '{'){PopLinkStack(ls);}else  // 类型不匹配{printf("当前符号错误 %c 行:%d 列:%d 或 栈顶符号 %c 行:%d 列:%d\n",*tmp, num, col, top->c, top->row, top->col);return 1;}break;}tmp++;  // 移动到下一个字符col++;  // 列号自增(记录下一个字符的位置)}return 0;  // 当前行检查无错误
}int main(int argc, char** argv)
{LinkStack* ls = CreateLinkStack();  // 创建一个空的链表栈,用于存储左括号// 打开待检查的文件(这里固定为"/home/linux/2.c")FILE* fp = fopen("/home/linux/2.c", "r");if (NULL == fp)  // 文件打开失败{printf("文件打开失败");return 1;}int num = 1;  // 记录当前处理的行号(从1开始计数)// 循环读取文件中的每一行,直到文件结束while (1){char buf[1024] = {0};  // 缓冲区,用于存储读取到的一行内容// 读取一行内容到buf(最多1023个字符,避免溢出)if (NULL == fgets(buf, sizeof(buf), fp)){break;  // 读取失败(文件结束),退出循环}// 检查当前行的括号匹配情况int ret = do_check(buf, ls, num);if (1 == ret)  // 发现错误{fclose(fp);          // 关闭文件DestroyLinkStack(ls);  // 销毁栈,释放内存return 1;            // 程序异常退出}num++;  // 行号自增(准备处理下一行)}// 所有行处理完毕后,检查栈是否为空if (IsEmptyLinkStack(ls)){printf("括号匹配正常");  // 栈为空:所有左括号都已匹配}else{// 栈不为空:存在未匹配的左括号,输出栈顶左括号的位置(最内层未匹配的左括号)DATATYPE* top = GetTopLinkStack(ls);printf("栈顶符号 %c 行:%d 列:%d 未匹配\n", top->c, top->row, top->col);}// 清理资源fclose(fp);          // 关闭文件DestroyLinkStack(ls);  // 销毁栈,释放内存return 0;            // 程序正常退出
}

2、使用 "双栈法" 解析算术表达式,通过两个栈分别存储数字和运算符,根据运算符优先级决定何时进行计算。

#include <stdio.h>      // 标准输入输出库
#include <string.h>     // 字符串处理库,提供bzero等函数
#include "LinkStack.h"  // 自定义链栈头文件,用于栈操作int num = 0;  // 全局变量,用于临时存储解析出的数字/*** @brief 将字符形式的数字累积转换为整数* @param c 字符形式的数字('0'~'9')*/
void get_num(char c)
{// 将当前字符数字累加到num中(例如"123"会被解析为1*10+2=12,再12*10+3=123)num = num * 10 + c - '0';
}/*** @brief 获取运算符的优先级* @param c 运算符(+、-、*、/)* @return 优先级:1(+、-),2(*、/),0(其他字符)*/
int get_priority(char c)
{switch (c){case '+':case '-':return 1;  // 加减优先级较低case '*':case '/':return 2;  // 乘除优先级较高default:return 0;  // 非运算符返回0}
}/*** @brief 计算两个数的运算结果* @param num1 第一个操作数(左操作数)* @param num2 第二个操作数(右操作数)* @param c 运算符* @return 运算结果*/
int get_result(int num1, int num2, char c)
{switch (c){case '+':return num1 + num2;  // 加法case '-':return num1 - num2;  // 减法case '*':return num1 * num2;  // 乘法case '/':return num1 / num2;  // 除法(整数除法)}return 0;  // 理论上不会执行到这里
}int main(int argc, char** argv)
{char* express = "20*3+5";  // 待计算的表达式LinkStack* ls_num;  // 数字栈:用于存储运算过程中的数字LinkStack* ls_sym;  // 符号栈:用于存储运算符char* tmp;          // 临时指针,用于遍历表达式DATATYPE* top;      // 指向栈顶元素的指针DATATYPE data;      // 用于存储栈操作的数据// 创建两个栈ls_num = CreateLinkStack();ls_sym = CreateLinkStack();tmp = express;  // 指向表达式的起始位置// 遍历表达式中的每个字符while (*tmp){bzero(&data, sizeof(data));  // 清空data结构体// 判断当前字符是否为数字if (*tmp >= '0' && *tmp <= '9'){get_num(*tmp);  // 累积数字(处理多位数)tmp++;          // 移动到下一个字符continue;       // 继续处理下一个字符}// 如果不是数字,说明遇到了运算符,将之前累积的数字入栈data.num = num;    // 将全局变量num的值存入datanum = 0;           // 重置num,准备解析下一个数字PushLinkStack(ls_num, &data);  // 将数字压入数字栈// 处理运算符入栈逻辑(保证栈顶运算符优先级不高于当前运算符)while (1){top = GetTopLinkStack(ls_sym);  // 获取符号栈顶元素// 如果符号栈为空,或栈顶运算符优先级低于当前运算符if (IsEmptyLinkStack(ls_sym) ||get_priority(top->sym) < get_priority(*tmp)){// 将当前运算符入栈bzero(&data, sizeof(data));data.sym = *tmp;  // 存储当前运算符PushLinkStack(ls_sym, &data);  // 压入符号栈break;  // 退出循环,继续处理表达式下一个字符}else{// 栈顶运算符优先级高于等于当前运算符,先计算栈顶运算符的结果// 从数字栈弹出两个操作数(注意顺序:后弹出的是左操作数)top = GetTopLinkStack(ls_num);int num2 = top->num;  // 右操作数PopLinkStack(ls_num);top = GetTopLinkStack(ls_num);int num1 = top->num;  // 左操作数PopLinkStack(ls_num);// 从符号栈弹出运算符top = GetTopLinkStack(ls_sym);char op = top->sym;   // 运算符PopLinkStack(ls_sym);// 计算结果并将结果压入数字栈int result = get_result(num1, num2, op);bzero(&data, sizeof(data));data.num = result;PushLinkStack(ls_num, &data);}}tmp++;  // 移动到下一个字符}// 表达式遍历结束后,将最后一个数字入栈data.num = num;num = 0;PushLinkStack(ls_num, &data);// 处理符号栈中剩余的运算符(计算所有剩余运算)while (!IsEmptyLinkStack(ls_sym)){// 弹出两个操作数top = GetTopLinkStack(ls_num);int num2 = top->num;PopLinkStack(ls_num);top = GetTopLinkStack(ls_num);int num1 = top->num;PopLinkStack(ls_num);// 弹出运算符并计算top = GetTopLinkStack(ls_sym);char op = top->sym;PopLinkStack(ls_sym);int result = get_result(num1, num2, op);// 将结果入栈bzero(&data, sizeof(data));data.num = result;PushLinkStack(ls_num, &data);}// 获取最终计算结果(数字栈中仅剩的一个元素)top = GetTopLinkStack(ls_num);int result = top->num;PopLinkStack(ls_num);// 输出结果printf("result %d\n", result);  // 对于表达式"20*3+5",结果应为65// 销毁栈,释放内存DestroyLinkStack(ls_num);DestroyLinkStack(ls_sym);return 0;
}

http://www.dtcms.com/a/344819.html

相关文章:

  • 鹰角网络基于阿里云 EMR Serverless StarRocks 的实时分析工程实践
  • CDN行业中的SA板卡限速是什么
  • 品牌出海狂潮里,独立站支付的「隐形基建」正在改写规则
  • java18学习笔记-JavaDoc的@snippet注释标签
  • 数据结构 -- 队列
  • 【运维自动化-标准运维】变量的高级用法
  • 去中心化的私有货币与中心化的法定货币的对比分析
  • 数据结构与算法-算法-283移动零
  • 深度分析AI边缘盒子在电力行业的应用与发展
  • 【LeetCode】22. 括号生成
  • 欲打造未来感十足的规划馆,应优先引入哪些沉浸式多媒体技术?
  • Spring Start Here 读书笔记:第9章 Using the Spring web scopes
  • 人脸识别驱动的工厂人体属性检测与预警机制
  • C#开源库ACadSharp读取dwg图元的示例
  • 为何她在“传递情报”时会被干扰?—— 探究 TCP 协议在无线环境中的信号干扰问题
  • 算法题复盘+代码解读(2)—— 两数之和
  • 【功能测试面试题】
  • 【数据结构】B+ 树——高度近似于菌丝网络——详细解说与其 C 代码实现
  • CVPR焦点 | 神经网络新范式:轻量化与精度并行,重塑视觉任务性能天花板
  • 解释一下,Linux,shell,Vmware,Ubuntu,以及Linux命令和shell命令的区别
  • 1337俚语的由来
  • Seaborn数据可视化实战:Seaborn时间序列可视化入门
  • Linux学习-网络编程2
  • .java->.class->java 虚拟机中运行
  • 51.Seata-TCC模式
  • 前端函数防抖
  • Nginx + Keepalived 实现高可用负载均衡集群
  • 前端桌面端解决方案技术选型:全面指南
  • 深入理解强化学习的target network
  • 3.5MM防水耳机插座