解码数据结构栈

news/2025/9/27 16:24:25/文章来源:https://www.cnblogs.com/YouEmbedded/p/19115148

栈的概念与特性

栈是线性结构的特殊形式,其设计初衷是解决 “数据需按特定顺序存取” 的场景(如函数调用、括号匹配),核心遵循 “后进先出”(LIFO,Last In First Out)原则,是计算机领域中最基础的数据结构之一。

关键定义与术语

术语 定义
栈(Stack) 仅允许在一端进行数据插入(入栈)和删除(出栈)的线性表
栈底(Bottom) 栈的封闭端,数据一旦入栈后,只有栈顶元素全部出栈后才能被访问
栈顶(Top) 栈的开放端,所有入栈、出栈操作均在此端进行,栈顶元素始终是 “最新入栈” 的数据
空栈 不包含任何元素的栈,此时栈顶指针(或下标)指向栈底外侧(如顺序栈 Top=-1)

核心操作

栈的基本操作需满足 “仅操作栈顶” 的约束,所有操作的时间复杂度均为O(1)(遍历除外,O (n)),具体如下:

操作名称 功能描述 关键约束
初始化(Init) 创建空栈,并为栈的管理结构(如结构体)分配内存,初始化栈底、栈顶、容量等参数 确保内存分配成功(避免空指针)
入栈(Push) 将数据插入栈顶,更新栈顶指针(或下标) 入栈前需判满(仅顺序栈需判满)
出栈(Pop) 移除栈顶数据,返回该数据,更新栈顶指针(或下标) 出栈前需判空
判空(IsEmpty) 判断栈是否为空(如顺序栈 Top=-1,链式栈栈顶指针为 NULL)
判满(IsFull) 仅顺序栈需实现,判断栈顶是否达到数组容量上限(Top=Size-1) 链式栈无需判满(动态分配)
取栈顶(GetTop) 返回栈顶数据,但不删除该数据 需先判空
遍历(Print) 从栈底到栈顶输出所有元素
销毁(Destroy) 释放栈占用的内存(顺序栈释放数组 + 管理结构体,链式栈释放所有节点 + 栈顶指针) 避免内存泄漏

栈的两种实现方式

栈作为线性表,可基于数组(顺序栈) 或单链表(链式栈) 实现。

顺序栈

  • 核心原理
    • 连续数组存储数据,数组下标 0 为栈底,栈顶随数据插入 / 删除动态移动(Top 初始化为 - 1,代表空栈)。
    • 需用结构体管理关键参数:栈底地址(数组指针)、栈容量(数组大小)、栈顶下标(Top)。
  • 定义
typedef int DataType_t;
// 2. 定义顺序栈管理结构体
typedef struct SequenceStack {DataType_t *Bottom;  // 栈底地址(指向数组首元素)unsigned int Size;   // 栈容量(数组最大存储元素个数)int Top;             // 栈顶下标(空栈时为-1,栈满时为Size-1)
} SeqStack_t;
  • 初始化
SeqStack_t *SeqStack_Init(unsigned int size) {// 分配管理结构体内存SeqStack_t *Manager = (SeqStack_t *)malloc(sizeof(SeqStack_t));if (Manager == NULL) {printf("Memory allocation failed for Manager!\n");return NULL;}// 分配栈数组内存Manager->Bottom = (DataType_t *)malloc(size * sizeof(DataType_t));if (Manager->Bottom == NULL) {printf("Memory allocation failed for Stack Array!\n");free(Manager);  // 先释放管理结构体,避免内存泄漏return NULL;}// 初始化参数Manager->Size = size;Manager->Top = -1;  // 空栈标志return Manager;
}
  • 判空
bool SeqStack_IsEmpty(SeqStack_t *Manager) {return Manager->Top == -1;
}
  • 判满
bool SeqStack_IsFull(SeqStack_t *Manager) {return Manager->Top == Manager->Size - 1;
}
  • 入栈
bool SeqStack_Push(SeqStack_t *Manager, DataType_t data) {// 先判满if (SeqStack_IsFull(Manager)) {printf("Stack is full! Push failed.\n");return false;}Manager->Top++;          // 栈顶上移Manager->Bottom[Manager->Top] = data;  // 存入数据return true;
}
  • 出栈
DataType_t SeqStack_Pop(SeqStack_t *Manager) {// 先判空if (SeqStack_IsEmpty(Manager)) {printf("Stack is empty! Pop failed.\n");return 0;  // 此处0为默认值,实际可通过返回bool+输出参数优化}DataType_t topData = Manager->Bottom[Manager->Top];  // 保存栈顶数据Manager->Top--;  // 栈顶下移(逻辑删除,无需真删除数据)return topData;
}
  • 取栈顶元素
DataType_t SeqStack_GetTop(SeqStack_t *Manager) {if (SeqStack_IsEmpty(Manager)) {printf("Stack is empty! GetTop failed.\n");return 0;}return Manager->Bottom[Manager->Top];
}
  • 遍历栈(栈底到栈顶)
void SeqStack_Print(SeqStack_t *Manager) {if (SeqStack_IsEmpty(Manager)) {printf("Stack is empty!\n");return;}printf("Stack Elements (Bottom -> Top): ");for (int i = 0; i <= Manager->Top; i++) {printf("%d ", Manager->Bottom[i]);}printf("\n");
}
  • 销毁
// 二级指针销毁函数 避免野指针
void SeqStack_Destroy(SeqStack_t **Manager) {// 先判断二级指针和目标指针是否有效if (Manager == NULL || *Manager == NULL) {return;  // 避免对空指针解引用}// 释放内部数组if ((*Manager)->Bottom != NULL) {free((*Manager)->Bottom);(*Manager)->Bottom = NULL;}// 释放管理结构体free(*Manager);*Manager = NULL;  // 关键:直接将外部指针置为NULL
}

注:

  • 顺序栈的容量固定,若需动态扩容,可在判满后重新分配更大数组(如原容量 2 倍),拷贝原数据后释放旧数组。
  • 入栈 / 出栈仅操作栈顶,时间复杂度 O (1);遍历需遍历所有元素,时间复杂度 O (n)。

链式栈

  • 核心原理
    • 单链表存储数据,链表头部作为栈顶(头插法入栈、头删法出栈,操作更高效),无需固定容量(动态分配节点)。
    • 仅需一个栈顶指针(指向链表首节点),空栈时栈顶指针为 NULL(无需栈底指针,链表尾节点即为栈底)。
  • 定义
typedef int DataType_t;
typedef struct LinkStackNode {DataType_t data;                // 节点数据struct LinkStackNode *next;     // 指向下一节点的指针
} LinkStackNode;
  • 初始化
LinkStackNode *LinkStack_Init() {return NULL;  // 空栈标志:栈顶指针为NULL
}
  • 判空
bool LinkStack_IsEmpty(LinkStackNode *top) {return top == NULL;
}
  • 入栈
// 入栈(头插法:新节点作为新栈顶)
LinkStackNode *LinkStack_Push(LinkStackNode *top, DataType_t data) {// 创建新节点LinkStackNode *newNode = (LinkStackNode *)malloc(sizeof(LinkStackNode));if (newNode == NULL) {printf("Memory allocation failed for new node!\n");return top;  // 入栈失败,返回原栈顶}// 新节点指向原栈顶newNode->data = data;newNode->next = top;// 新节点成为新栈顶return newNode;
}
  • 出栈
//  出栈(头删法:删除栈顶节点,返回新栈顶)
LinkStackNode *LinkStack_Pop(LinkStackNode *top, DataType_t *outData) {// 先判空if (LinkStack_IsEmpty(top)) {printf("Stack is empty! Pop failed.\n");*outData = 0;return top;}// 保存原栈顶节点和数据LinkStackNode *oldTop = top;*outData = oldTop->data;// 新栈顶为原栈顶的下一节点top = top->next;// 释放原栈顶节点free(oldTop);oldTop = NULL;return top;
}
  • 取栈顶元素
bool LinkStack_GetTop(LinkStackNode *top, DataType_t *outData) {if (LinkStack_IsEmpty(top)) {printf("Stack is empty! GetTop failed.\n");return false;}*outData = top->data;return true;
}
  • 遍历(从栈顶到栈底)
void LinkStack_Print(LinkStackNode *top) {if (LinkStack_IsEmpty(top)) {printf("Stack is empty!\n");return;}LinkStackNode *p = top;  // 辅助指针printf("Stack Elements (Top -> Bottom): ");while (p != NULL) {printf("%d ", p->data);p = p->next;}printf("\n");
}
  • 销毁
LinkStackNode *LinkStack_Destroy(LinkStackNode *top) {LinkStackNode *p = top;while (p != NULL) {LinkStackNode *temp = p;  // 保存当前节点p = p->next;              // 移动到下一节点free(temp);               // 释放当前节点temp = NULL;}return NULL;  // 销毁后栈顶为NULL
}

顺序栈与链式栈的对比

对比维度 顺序栈(数组) 链式栈(单链表)
存储方式 连续内存空间(数组) 不连续内存空间(节点动态分配)
空间效率 可能浪费空间(数组容量固定,未用满) 无空间浪费(按需分配节点)
时间效率 入栈 / 出栈 O (1),扩容 O (n)(可选) 入栈 / 出栈 O (1)(头插 / 头删)
判满需求 必须判满(数组容量固定) 无需判满(动态扩展)
实现复杂度 较简单(数组操作) 较复杂(节点指针管理,需避免内存泄漏)
适用场景 已知数据规模、需高效存取的场景 数据规模不确定、需灵活扩展的场景

栈的典型应用

应用 1:十进制转十六进制(处理 0 值)

  • 核心逻辑
    • 原理:十进制数反复除以 16,余数作为十六进制位(逆序排列,需用栈存储余数,最后出栈即正序)。
    • 补充:若输入为 0,需单独处理(原 do-while 循环不执行,需手动入栈 '0')。

代码

void SeqStack_Dec2Hex(SeqStack_t *Manager, unsigned int Data) {// 处理Data=0的情况(否则do-while不执行,栈为空)if (Data == 0) {SeqStack_Push(Manager, '0');} else {int remainder;do {remainder = Data % 16;// 余数映射:0-9→'0'-'9',10-15→'A'-'F'if (remainder < 10) {SeqStack_Push(Manager, remainder + '0');} else {SeqStack_Push(Manager, remainder - 10 + 'A');}Data /= 16;} while (Data != 0);}// 出栈输出(栈中为逆序,出栈后为正序)printf("Hex Result: 0x");while (!SeqStack_IsEmpty(Manager)) {printf("%c", (char)SeqStack_Pop(Manager));}printf("\n");
}// 测试:输入0→0x0;输入255→0xFF;输入10→0xA

应用 2:有效括号判断(支持多类型括号)

  • 核心逻辑
    • 需求:判断字符串中()[]{}是否配对(左括号入栈,右括号与栈顶匹配,最后栈需为空)。
    • 补充:原笔记仅处理(),此处扩展多类型括号,且增加 “右括号无匹配左括号”“左括号未闭合” 的判断。

代码

// 有效括号判断(支持()、[]、{})
bool SeqStack_IsValidBracket(SeqStack_t *Manager, const char *str) {if (str == NULL) return false;  // 空字符串无效const char *p = str;while (*p != '\0') {// 左括号:入栈if (*p == '(' || *p == '[' || *p == '{') {SeqStack_Push(Manager, *p);}// 右括号:判断匹配else if (*p == ')' || *p == ']' || *p == '}') {// 若栈空,右括号无匹配左括号→无效if (SeqStack_IsEmpty(Manager)) {return false;}// 取出栈顶左括号,判断是否匹配char topBracket = (char)SeqStack_Pop(Manager);if ((*p == ')' && topBracket != '(') ||(*p == ']' && topBracket != '[') ||(*p == '}' && topBracket != '{')) {return false;  // 类型不匹配}}// 非括号字符:忽略(若需求为“仅括号”,可返回false)p++;}// 遍历结束后,栈需为空(所有左括号均闭合)return SeqStack_IsEmpty(Manager);
}// 测试:"()[]{}"→true;"([)]"→false;"({})"→true;"("→false

其他应用场景

  • 函数调用栈:C 语言中,函数调用时会创建 “栈帧”(存储返回地址、局部变量、寄存器值),函数执行完后栈帧出栈,回到调用处(遵循 LIFO)。

  • 表达式求值:将中缀表达式(如3+4*2)转为后缀表达式(3 4 2 * +),用栈处理运算符优先级,再通过栈计算后缀表达式结果。

    中缀转后缀

  • 函数代码

    // 中缀表达式转后缀表达式
    void infixToPostfix(char* infix, char* postfix) {int i, j;Stack* stack = createStack(strlen(infix));for (i = 0, j = 0; infix[i]; ++i) {// 如果是操作数,直接添加到后缀表达式if (isdigit(infix[i]) || infix[i] == '.') {postfix[j++] = infix[i];}// 如果是左括号,入栈else if (infix[i] == '(') {pushOperator(stack, infix[i]);}// 如果是右括号,弹出所有运算符直到遇到左括号else if (infix[i] == ')') {while (!isEmpty(stack) && peekOperator(stack) != '(') {postfix[j++] = ' ';  // 用空格分隔不同的元素postfix[j++] = popOperator(stack);}popOperator(stack);  // 弹出左括号,不加入后缀表达式}// 如果是运算符else if (isOperator(infix[i])) {postfix[j++] = ' ';  // 用空格分隔不同的元素// 弹出所有优先级大于等于当前运算符的运算符while (!isEmpty(stack) && precedence(peekOperator(stack)) >= precedence(infix[i])) {postfix[j++] = popOperator(stack);postfix[j++] = ' ';}pushOperator(stack, infix[i]);}}// 弹出栈中剩余的所有运算符while (!isEmpty(stack)) {postfix[j++] = ' ';postfix[j++] = popOperator(stack);}postfix[j] = '\0';free(stack->operatorItems);free(stack->numberItems);free(stack);
    }// 计算后缀表达式
    double evaluatePostfix(char* postfix) {Stack* stack = createStack(strlen(postfix));double num = 0, decimal = 0, decimalPlace = 0;int i;for (i = 0; postfix[i]; ++i) {// 如果是数字或小数点,解析为数字if (isdigit(postfix[i]) || postfix[i] == '.') {if (postfix[i] == '.') {decimal = 1;decimalPlace = 1;continue;}if (decimal) {num += (postfix[i] - '0') / pow(10, decimalPlace);decimalPlace++;} else {num = num * 10 + (postfix[i] - '0');}}// 如果是空格且之前有数字,将数字入栈else if (postfix[i] == ' ' && (isdigit(postfix[i-1]) || postfix[i-1] == '.')) {pushNumber(stack, num);num = 0;decimal = 0;}// 如果是运算符,弹出两个数字进行运算else if (isOperator(postfix[i])) {double val1 = popNumber(stack);double val2 = popNumber(stack);double result;switch (postfix[i]) {case '+':result = val2 + val1;break;case '-':result = val2 - val1;break;case '*':result = val2 * val1;break;case '/':if (val1 == 0) {printf("错误:除数不能为零!\n");return NAN;}result = val2 / val1;break;case '^':result = pow(val2, val1);break;default:result = 0;}pushNumber(stack, result);}}double finalResult = popNumber(stack);free(stack->operatorItems);free(stack->numberItems);free(stack);return finalResult;
    }
    
  • 浏览器前进后退:用两个栈(forwardStackbackStack),浏览新页面时将当前页面压入backStack;点击后退时将backStack栈顶弹出,压入forwardStack并显示;点击前进时反之。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/919658.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vs 2008 建立网站汕头有几个区几个县

七燕论文是一个非常好用的论文写作工具&#xff0c;它不仅可以帮助学生提高写作效率&#xff0c;还能帮助他们避免抄袭和提高论文质量。七燕论文的查重降重功能非常靠谱&#xff0c;能够帮助用户检测论文中的重复内容&#xff0c;并提供相应的修改建议&#xff0c;确保论文的原…

第七章 手写数字识别V4

# 优化: # 增加父类Module,输出每层信息 # 增加ReLU类,Tanh类 # 增加Dropout类,随机失活,防止过拟合,提高泛化能力 # 增加Parameter类,保存权重和梯度# 导入必要的库 import numpy as np import os import stru…

什么?你的蓝牙用不了了?

什么?你的蓝牙用不了了?如果你的电脑蓝牙出现一下问题:蓝牙图标不见? 搜索不到任何设备? 只能搜到手机不能搜到耳机? 看看本篇文章给你的解决办法把!蓝牙图标不见 暂未解决。 搜索不到任何设备 首先,同时按下 …

个人可以做电影网站吗信用徐州网站建设情况

黑马程序员上海中心学姐微信&#xff1a;CZBKSH关注咳咳&#xff0c;今天学姐就来和你们说说Spring对于Java程序员的重要性。首先&#xff0c;Spring 官网首页是这么介绍自己的——“Spring: the source for modern Java”&#xff0c;这也意味着 Spring 与 Java 有着密切的关系…

做韦恩图的在线网站wordpress下载视频

简单选择排序的介绍&#xff1a;从给定的序列中&#xff0c;按照指定的规则选出某一个元素&#xff0c;再根据规定交换位置后达到有序的目的。简单选择排序的基本思想&#xff1a;假定我们的数组为int [] arr new int[n]&#xff0c;第一次我们从arr[0]~arr[n-1]中选择出最小的…

2025/9/27

2025/9/271.完成课后任务:验证码任务 2.完成课后任务:生成三十道四则运算题

30.Linux DHCP 服务器 - 详解

30.Linux DHCP 服务器 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco"…

C# Smart3D Plate Part零件形状提取

public class ExportPartShape : BaseModalCommand{public override void OnStart(int instanceId, object argument){base.OnStart(instanceId, argument);var symFile = @"C:\Program Files (x86)\Smart3D\Comm…

威海建设局网站首页图片编辑在线

在FTP协议中&#xff0c;可以通过配置服务器端的空闲连接超时时间来设置连接的过期时间。具体步骤如下&#xff1a; 登录FTP服务器&#xff0c;进入服务器的配置文件目录。通常配置文件位于/etc或/etc/vsftpd目录下。打开FTP服务器的配置文件&#xff0c;例如vsftpd.conf。在配…

网站使用微软雅黑小程序模板免费下载

C语言实验lab10C程序设计实验报告学院&#xff1a;国际商学院班级&#xff1a;14电商专业&#xff1a;电子商务姓名&#xff1a;熊靓男日期&#xff1a;15.5.25学号&#xff1a;1420070049实验目的复习一维数组掌握二维数组参数的传递掌握排序算法实验内容消灭怪物在阳光明媚月…

化妆品网站系统规划网站制作排名

import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可** * param s string字符串 * param n int整型 * return string字符串*/public String trans (String s, int n) {// write co…

长春网站建设公司会展设计效果图

作者前言 欢迎小可爱们前来借鉴我的gtiee秦老大大 (qin-laoda) - Gitee.com —————————————————————————————— 目录 查询数据 条件 逻辑运算符 模糊查询 范围查询 in 判断空 UNION 排序 聚合 分组&#xff1a;group by —————————…

网站开发工程师的证件seo技术专员招聘

本文简单记录一次实践使用过程&#xff0c;涉及presto-mysql,presto-elasticsearch&#xff0c;文中参数未做注释&#xff0c;请参考官方文档&#xff0c;希望能帮到大家1 下载安装 presto-0.228<1>下载服务端客户端相关jar<2>安装&#xff1a;1> 解压tar -zxvf…

路飞和女帝做h的网站女装网站建设计划书

正则表达式是一个特殊的字符序列&#xff0c;它能帮助你方便的检查一个字符串是否与某种模式匹配。re 模块使 Python 语言拥有全部的正则表达式功能。compile 函数根据一个模式字符串和可选的标志参数生成一个正则表达式对象。该对象拥有一系列方法用于正则表达式匹配和替换。r…

潍坊市住房和城乡建设厅网站如何自己制作链接内容

第一步&#xff1a;安装svg-sprite-loader插件 <!-- svg-sprite-loader svg雪碧图 转换工具 --> <!-- <symbol> 元素中的 path 就是绘制图标的路径&#xff0c;这种一大串的东西我们肯定没办法手动的去处理&#xff0c; 那么就需要用到插件 svg-sprite-loader …

用户体验好的网站wordpress用户修改头像

在处理多个 Python 库依赖时&#xff0c;遇到依赖冲突是很常见的&#xff0c;特别是当项目依赖的库版本相互不兼容时。要解决这些冲突&#xff0c;可以采用以下方式。 1. 虚拟环境的使用 为了避免系统级和用户级包的冲突&#xff0c;建议你使用 虚拟环境。虚拟环境为每个项目…

题解:QOJ9619/洛谷13568 [CCPC 2024 重庆站] 乘积,欧拉函数,求和(数论+状压DP)

首先将 \(\phi(x)\) 拆成 \(\phi(x)= x \prod_{p | x} \frac {p-1}{p}\),发现我们要求的式子其实可以转化为 \(\sum_{S} (\prod a_i)\prod_{p|\prod a_i} \frac {p-1}{p}\)。 发现其实我们只关心哪些质数 \(p\) 在最终…

Momentum Gradient Descent(动量梯度下降)

Momentum Gradient Descent(动量梯度下降)是标准梯度下降(SGD)的一个重要改进版,旨在加速训练过程,并帮助模型更有效地找到最优解。 你可以将动量(Momentum)想象成物理学中的惯性。动量梯度下降(Momentum GD)…

Halcon算子——2D几何变换

齐次坐标 介绍仿射变换前,先介绍什么是齐次坐标。对于一个平面像素点,我们可以通过坐标(x,y)描述其位置。但是当涉及平移时,如果仅仅使用它对应的坐标向量[x,y],就必须通过向量加法来描述其位移。 而齐次坐标的引入…

深入解析:深度解析 CUDA-QX 0.4 加速 QEC 与求解器库

深入解析:深度解析 CUDA-QX 0.4 加速 QEC 与求解器库pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&qu…