数据结构C语言描述4(图文结合)--栈的实现,中序转后序表达式的实现

前言

  • 这个专栏将会用纯C实现常用的数据结构和简单的算法;
  • 有C基础即可跟着学习,代码均可运行;
  • 准备考研的也可跟着写,个人感觉,如果时间充裕,手写一遍比看书、刷题管用很多,这也是本人采用纯C语言实现的原因之一;
  • 欢迎收藏 + 关注,本人将会持续更新。

文章目录

    • 什么是栈
    • 栈的代码实现
      • 数组栈
      • 链式栈
    • 栈的应用
      • 求和存储数据的二进制
      • 表达式求值详解
        • 中缀表达式
        • 后缀表达式
        • 中缀转后缀

什么是栈

栈是一种运算受限的线性表,它限定只能在表的一端进行插入和删除操作。

栈0的结构特性是:后进先出LIFO ( Last In First Out)。栈又被称为后进先出表。

两个重要概念:

栈顶:允许插入和删除的一端称作栈顶;

栈底:不允许插入和删除的一端称作栈底。


数组栈结构如下:

在这里插入图片描述

其中:

  • 栈底元素:a1
  • 栈顶元素:an
  • 入栈顺序:a1, a2, …, an 依次入栈;
  • 出栈顺序:an, an-1, …, a1,先入的后出 ,只能在栈顶进行插入和删除。

链式栈结构:

在这里插入图片描述

链式栈原理就是:头插入、头删除。

栈的代码实现

ADT抽象操作:

  • 创建栈
  • 入栈
  • 出栈
  • 获取栈顶元素
  • 栈是否为空
  • 栈元素个数

数组栈

解释:用数组模拟栈。

📦 栈封装

封装元素:

  • 数据
  • 数组当前能存储的最大大小(容量)
  • 栈顶游标
typedef struct Stack {DataType* data;size_t maxSize;    // int top;
}Stack;

💳 创建栈与栈初始化

这个步骤目标:创建栈,并且初始化栈变量;

栈顶初始化:-1,入栈:++top,出战:top--

Stack* create_stack()
{Stack* stack = (Stack*)calloc(1, sizeof(Stack));assert(stack);stack->top = -1;   // 栈顶初始值为 -1return stack;
}

📌 入栈

操作:就是数组向后添加元素

注意:就是扩容操作,这里不够就**++10**

void push(Stack* stack, DataType data)
{assert(stack);// 扩容if (stack->top == stack->maxSize - 1) {DataType* temp = (DataType*)realloc(stack->data, (stack->maxSize + 10) * sizeof(DataType));assert(temp);stack->maxSize += 10;stack->data = temp;}stack->data[++stack->top] = data;
}

🔝 获取栈顶元素和出栈

获取栈顶:获取数组下标为top元素

出栈:top减1

DataType top(Stack* stack)
{assert(stack);return stack->data[stack->top];
}void pop(Stack* stack)
{assert(stack);stack->top--;
}

🚄 万金油函数

  • 获取栈大小
  • 栈是否为空

这两个操作,没什么难度,看代码即可;

bool empty(Stack* stack)
{assert(stack);return stack->top == -1;
}size_t size(Stack* stack)
{assert(stack);return stack->top + 1;
}

⚗️ 总代码

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>typedef int DataType;typedef struct Stack {DataType* data;size_t maxSize;int top;
}Stack;Stack* create_stack()
{Stack* stack = (Stack*)calloc(1, sizeof(Stack));assert(stack);stack->top = -1;   // 栈顶初始值为 -1return stack;
}void push(Stack* stack, DataType data)
{assert(stack);// 扩容if (stack->top == stack->maxSize - 1) {DataType* temp = (DataType*)realloc(stack->data, (stack->maxSize + 10) * sizeof(DataType));assert(temp);stack->maxSize += 10;stack->data = temp;}stack->data[++stack->top] = data;
}DataType top(Stack* stack)
{assert(stack);return stack->data[stack->top];
}void pop(Stack* stack)
{assert(stack);stack->top--;
}bool empty(Stack* stack)
{assert(stack);return stack->top == -1;
}size_t size(Stack* stack)
{assert(stack);return stack->top + 1;
}int main()
{Stack* stack = create_stack();for (int i = 1; i <= 10; i++) {push(stack, i);}printf("size: %llu\n", size(stack));while (!empty(stack)) {printf("%d ", top(stack));pop(stack);}putchar('\n');printf("size: %llu\n", size(stack));return 0;
}

链式栈

🌇 实现方式:采用无头链表;

🌙 模拟:采用链表的头插和弹出头,来模拟入栈和出栈操作。


📦 栈封装

节点封装

  • 存储元素、指针指向下一个节点

链表封装

  • 采用在封装写法,创建一个指针,指向头节点,同时定义size,存储当前栈的大小,是这个在封装写法的核心。🤔 为什么说size是核心变量??,图示如下:
typedef int DataType;typedef struct Node {DataType data;struct Node* next;
}Node;typedef struct Stack {Node* headNode;int size;
}Stack;

🖍 封装创建节点和创建栈函数

Node* create_node(DataType data)
{Node* node = (Node*)calloc(1, sizeof(Node));assert(node);node->data = data;return node;
}Stack* create_stack()
{Stack* stack = (Stack*)calloc(1, sizeof(Stack));assert(stack);return stack;
}

📌 元素入栈

在封装写法的核心是变量size,当size==0的时候,说明这个时候是链表为空,这个时候插入就是创建头节点,其他情况就是正常头插,这样引入变量也可以避免指针指向被修改的问题,不需要采用二级指针。

图:

// 头插
void push(Stack* stack, DataType data)
{assert(stack);Node* newNode = create_node(data);if (stack->size == 0) {stack->headNode = newNode;}else {newNode->next = stack->headNode;stack->headNode = newNode;}stack->size++;
}

🤕 出栈和获取栈顶元素

获取栈顶:就是获取头节点元素过程;

出战:删除头节点,要注意的就是栈为空的情况。

图示:

DataType top(Stack* stack)
{assert(stack);assert(stack->headNode);return stack->headNode->data;
}void pop(Stack* stack)
{assert(stack);if (stack->size == 0) {return;}else {Node* temp = stack->headNode;stack->headNode = stack->headNode->next;free(temp);temp = NULL;}stack->size--;}

💛 万金油函数

采用在封装写法对获取元素和判断是否为空,有着天然的优势,代码如下:

bool empty(Stack* stack)
{assert(stack);return stack->size == 0;
}int size(Stack* stack)
{assert(stack);return stack->size;
}

总代码

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>// 采用无头链表实现typedef int DataType;typedef struct Node {DataType data;struct Node* next;
}Node;typedef struct Stack {Node* headNode;int size;
}Stack;Node* create_node(DataType data)
{Node* node = (Node*)calloc(1, sizeof(Node));assert(node);node->data = data;return node;
}Stack* create_stack()
{Stack* stack = (Stack*)calloc(1, sizeof(Stack));assert(stack);return stack;
}// 头插
void push(Stack* stack, DataType data)
{assert(stack);Node* newNode = create_node(data);if (stack->size == 0) {stack->headNode = newNode;}else {newNode->next = stack->headNode;stack->headNode = newNode;}stack->size++;
}DataType top(Stack* stack)
{assert(stack);assert(stack->headNode);return stack->headNode->data;
}void pop(Stack* stack)
{assert(stack);if (stack->size == 0) {return;}else {Node* temp = stack->headNode;stack->headNode = stack->headNode->next;free(temp);temp = NULL;}stack->size--;}bool empty(Stack* stack)
{assert(stack);return stack->size == 0;
}int size(Stack* stack)
{assert(stack);return stack->size;
}int main()
{Stack* stack = create_stack();for (int i = 1; i <= 10; i++) {push(stack, i);}while (!empty(stack)) {printf("%d ", top(stack));pop(stack);}return 0;
}

栈的应用

其实在开发中用栈,要不用封装好容器,要么用数组模拟,不会像上面那种方式手写。

求和存储数据的二进制

要求:求一个数的二进制

// 一般栈开发中都有容器,直接调用,哪怕是C语言也是简单的模拟,如下
void test_stack()
{// 用栈存储并打印 666 的二进制int stack[1024] = { 0 };   // 数组模拟栈int top = -1;              // 定义栈顶int num = 666;int t = num;while (t) {stack[++top] = ((t & 1) == 1) ? 1 : 0;  // 入栈t >>= 1;}// 输出和弹出栈顶while (top != -1) {printf("%d", stack[top--]);  // 获取栈顶与出栈}
}

表达式求值详解

中缀表达式

我们把平时所用的标准四则运算表达式,即“9+(3-1)×3+10÷2”叫做中缀表达式。因为所有的运算符号都在两数字的中间。

后缀表达式

后缀表达式也叫作逆波兰表达式,对于“9+(3-1)×3+10÷2”,如果要用后缀表示法应该是什么样子:“9 3 1-3*+10 2/+”,这样的表达式称为后缀表达式,叫后缀的原因在于所有的符号都是在要运算数字的后面出现。后缀表达式如何求解表达式的值?

  • 遍历后缀表达式,如果遇到数字则直接入栈,如果遇到操作符,则弹出栈顶的两个元素,进行计算后将结果入栈。
  • 最终,栈内剩余的元素就是整个表达式的计算结果。

在这里插入图片描述

中缀转后缀

操作流程

  • 如果栈顶元素的优先级大于等于当前操作符,则先将栈顶元素弹出并输出到后缀表达式中,再将当前操作符压入栈中。
  • 如果遇到了左括号,则直接将其压入栈中,如果遇到了右括号,则弹出栈中的元素,直到遇到了左括号为止,并将这些元素输出到后缀表达式中。
  • 最后,将栈中剩余的元素依次弹出,并输出到后缀表达式中。

在这里插入图片描述

代码实现

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>/*
* 中缀:a + b * (c + d) / e - f
* 后缀:a b + c d + * e / f -
*/#define MAX_SIZE 2014
char stack[MAX_SIZE];  // 定义栈
int top = -1;bool isdigits(char figure)
{return ((figure - '0' >= 0) && (figure - '0' <= 9));
}bool is_operator(char op)
{return op == '+' || op == '-' || op == '*' || op == '/';
}int pri_operator(char key)
{switch (key){case '+':case '-':return 1;case '*':case '/':return 2;}return 0;
}void midToLast(char* p, char* q)
{assert(p);assert(q);while (*p != '\0') {// 是数字 255+43if (isdigits(*p)) {  // 是数字while (isdigits(*p) || *p == '.') {*q = *p;p++;q++;}// 加空格,输出好看好一点*q = ' ';q++;}else if (is_operator(*p)) {    // 运算符if (top == -1) {   // 栈空stack[++top] = *p;p++;}else if (pri_operator(stack[top]) >= pri_operator(*p)) {   // 栈顶符号 优先即大于等于 当先运算符while (top != -1 && is_operator(stack[top])) {    // 弹出*q = stack[top--];    q++;// 加空格,输出好看好一点*q = ' ';q++;}}else {stack[++top] = *p;p++;}}else if (*p == '(') {stack[++top] = *p;p++;}else if (*p == ')') {while (top != -1 && stack[top] != '(') {*q = stack[top--];q++;// 加空格,输出好看好一点*q = ' ';q++;}if (top != -1) {  // 不是-1,就是左括号top--;}p++;}}// 剩余运算while (top != -1) {*q = stack[top--];q++;// 加空格,输出好看好一点*q = ' ';q++;}*q = '\0';
}int main()
{// 10 33 22 - 10 * + 12 4 / + 0.25 +char midExpression[MAX_SIZE] = { "10+(33-32)*10+12/4+0.25" };char lastExpression[MAX_SIZE * 2] = "";midToLast(midExpression, lastExpression);printf("%s\n", lastExpression);return 0;
}/*输出:
10 33 32 - 10 * + 12 4 / + 0.25 +输出:
*/

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

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

相关文章

对比 MyBatis 批处理 BATCH 模式与 INSERT INTO ... SELECT ... UNION ALL 进行批量插入

前言 在开发中&#xff0c;我们经常需要批量插入大量数据。不同的批量插入方法有不同的优缺点&#xff0c;适用于不同的场景。本文将详细对比两种常见的批量插入方法&#xff1a; MyBatis 的批处理模式。使用 INSERT INTO ... SELECT ... UNION ALL 进行批量插入。 MyBatis …

vue中路由缓存

vue中路由缓存 问题描述及截图解决思路关键代码及打印信息截图 问题描述及截图 在使用某一平台时发现当列表页码切换后点击某一卡片进入详情页后&#xff0c;再返回列表页时页面刷新了。这样用户每次看完详情回到列表页都得再重新输入自己的查询条件&#xff0c;或者切换分页到…

第N8周:使用Word2vec实现文本分类

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 本周任务&#xff1a; 结合Word2Vec文本内容预测文本标签 加载数据 import torch import torch.nn as nn import torchvision from torchvision import tra…

如何在 UniApp 中实现 iOS 版本更新检测

随着移动应用的不断发展&#xff0c;保持应用程序的更新是必不可少的&#xff0c;这样用户才能获得更好的体验。本文将帮助你在 UniApp 中实现 iOS 版的版本更新检测和提示&#xff0c;适合刚入行的小白。我们将分步骤进行说明&#xff0c;每一步所需的代码及其解释都会一一列出…

FreeRTOS之vTaskDelete实现分析

这里写自定义目录标题 1 函数接口1.1 函数接口1.2 函数参数简介 2 vTaskDelete的调用关系2.1 调用关系2.2 调用关系示意图 3 函数源码分析3.1 vTaskDelete3.2 uxListRemove 1 函数接口 1.1 函数接口 void vTaskDelete( TaskHandle_t xTaskToDelete )1.2 函数参数简介 TaskHa…

移动充储机器人“小奥”的多场景应用(上)

一、高速公路服务区应用 在高速公路服务区&#xff0c;新能源汽车的充电需求得到“小奥”机器人的及时响应。该机器人配备有储能电池和自动驾驶技术&#xff0c;能够迅速定位至指定充电点&#xff0c;为待充电的新能源汽车提供服务。得益于“小奥”的机动性&#xff0c;其服务…

C语言实例_5之根据输入年月日,计算属于该年的第几天

1. 题目 输入某年某月某日&#xff0c;判断这一天是这一年的第几天&#xff1f; 2. 分析 步骤1:得先判断年份是否是闰年&#xff0c;是的话&#xff0c;当月份大于3时&#xff0c;需多加一天&#xff1b; 步骤2:还需根据输入月份&#xff0c;判断输入天数是否合理&#xff0…

Semaphore 信号量

文章目录 基本概念工作原理Semaphore 与 ReentrantLockSemaphore常用场景1. 限制并发线程数&#xff08;最常见场景&#xff09;2. 公平模式的信号量&#xff08;保证按顺序访问资源&#xff09;3. 限制数据库连接数&#xff08;模拟数据库连接池&#xff09;4. 限制 API 请求次…

Redis 的代理类注入失败,连不上 redis

在测试 redis 是否成功连接时&#xff0c;发现 bean 没有被创建成功&#xff0c;导致报错 根据报错提示&#xff0c;需要我们添加依赖&#xff1a; <dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>&l…

桌面怎么快速添加便签?适合桌面记事的便签小工具

在数字化时代&#xff0c;我们每天面对电脑处理大量任务&#xff0c;无论是工作计划、会议纪要还是个人生活琐事&#xff0c;都需要一个可靠的桌面记事工具来帮助我们记录和整理。因此&#xff0c;一款适合桌面使用的便签软件成为了我们不可或缺的助手。 敬业签就是这样一款功…

UE5 腿部IK 解决方案 footplacement

UE5系列文章目录 文章目录 UE5系列文章目录前言一、FootPlacement 是什么&#xff1f;二、具体实现 前言 在Unreal Engine 5 (UE5) 中&#xff0c;腿部IK&#xff08;Inverse Kinematics&#xff0c;逆向运动学&#xff09;是一个重要的动画技术&#xff0c;用于实现角色脚部准…

KLV6008固态继电器:高压应用的理想紧凑方案

在当今快节奏的电子领域&#xff0c;找到平衡性能、可靠性和安全性的组件至关重要。CRIA Semiconductor的KLV6008固态继电器(SSR)正是满足了这一要求。这款紧凑型继电器专为高压、低电流切换而设计&#xff0c;是适用于各种应用的多功能解决方案。 为什么选择KLV6008&#xff1…

如何在 React 项目中应用 TypeScript?应该注意那些点?结合实际项目示例及代码进行讲解!

在 React 项目中应用 TypeScript 是提升开发效率、增强代码可维护性和可读性的好方法。TypeScript 提供了静态类型检查、自动补全和代码提示等功能&#xff0c;这对于 React 开发者来说&#xff0c;能够帮助早期发现潜在的 bug&#xff0c;提高开发体验。 1. 项目初始化 在现…

解锁生成式AI的真实价值:衡量ROI的12步框架

在当今快速发展的技术环境中,生成式AI正逐渐成为企业创新和增长的重要驱动力。然而,随着数十亿美元的投资涌入生成式AI项目,一个严峻的问题浮出水面:如何衡量这些投资的回报(ROI)?本文将探讨生成式AI ROI衡量的挑战,并提供一个12步框架,帮助公司有效地评估和最大化其生…

【网络云计算】2024第48周-每日【2024/11/20】小测-理论题-计算机网络概述

文章目录 1、计算机常见的网络设备有哪些&#xff1f;2、进制换算3、写出你认为的如何才能学好网络知识4、写出你知道的网络相关的求职岗位有哪些&#xff1f; 【网络云计算】2024第48周-每日【2024/11/20】小测-理论题- 1、计算机常见的网络设备有哪些&#xff1f; 2、进制换…

在 Swift 中实现字符串分割问题:以字典中的单词构造句子

文章目录 前言摘要描述题解答案题解代码题解代码分析示例测试及结果时间复杂度空间复杂度总结 前言 本题由于没有合适答案为以往遗留问题&#xff0c;最近有时间将以往遗留问题一一完善。 LeetCode - #140 单词拆分 II 不积跬步&#xff0c;无以至千里&#xff1b;不积小流&…

HarmonyOs鸿蒙开发实战(21)=>组件间通信@ohos/liveeventbus

1.简介 LiveEventBus是一款消息总线&#xff0c;具有生命周期感知能力&#xff0c;支持Sticky&#xff0c;支持跨进程&#xff0c;支持跨APP发送消息。 2.下载安装 ohpm install ohos/liveeventbus 3.订阅&#xff0c;注册监听 4.发送事件 5. 完成 > 记得关注博主&#xff…

OpenCV和Qt坐标系不一致问题

“ OpenCV和QT坐标系导致绘图精度下降问题。” OpenCV和Qt常用的坐标系都是笛卡尔坐标系&#xff0c;但是细微处有些不同。 01 — OpenCV坐标系 OpenCV是图像处理库&#xff0c;是以图像像素为一个坐标位置&#xff0c;即一个像素对应一个坐标&#xff0c;所以其坐标系也叫图像…

单片机结合OpenCV

目录 一、引言 二、单片机结合 OpenCV 的优势 1. 图像识别与处理 2. 目标检测 3. 用户界面开发 4. Linux 在嵌入式系统中的作用 5. 多线程优势 6. 网络编程作用 7. 文件编程功能 三、OpenCV 在单片机上的实现难点 1. 处理能力限制 2. 通信与优化挑战 四、单片机如…

nohup java -jar supporterSys.jar --spring.profiles.active=prod

文章目录 1、ps -ef | grep java2、kill 13713、ps -ef | grep java4、nohup java -jar supporterSys.jar --spring.profiles.activeprod &5、ps -ef | grep java1. 启动方式进程 1371进程 19994 2. 主要区别3. 可能的原因4. 建议 1、ps -ef | grep java rootshipper:~# p…