深入理解算数表达式求值:后缀表达式的转换与计算

归纳编程学习的感悟,
记录奋斗路上的点滴,
希望能帮到一样刻苦的你!
如有不足欢迎指正!
共同学习交流!
🌎欢迎各位→点赞 👍+ 收藏⭐ + 留言​📝

没人会嘲笑竭尽全力的人!

前言

在计算机科学中,算数表达式的求值是一个经典的问题,尤其是在编译器设计和计算器应用中。中缀表达式,即我们日常所见的数学表达式(如 3 + 4(2 + 3) * 4),虽然直观易懂,但对于计算机来说并不友好,因为它们需要遵循特定的运算优先级规则。因此,将中缀表达式转换为后缀表达式(也称为逆波兰记法或RPN)可以简化计算过程,使计算机能够更有效地进行求值。

后缀表达式简介

后缀表达式的特点是运算符紧跟在其操作数之后,例如,中缀表达式 3 + 4 转换为后缀表达式后成为 3 4 +。这种格式消除了对运算符优先级的依赖,也不需要使用括号来表示运算的顺序,这使得计算变得更为直接和高效。

中缀到后缀的转换

步骤概述
  1. 初始化:创建一个空的运算符栈和一个空的数组。
  2. 遍历输入:逐个检查中缀表达式中的每个字符。
    • 如果是操作数,直接将其添加到数组。
    • 如果是运算符,根据栈顶运算符的优先级与当前运算符比较:
      • 若当前运算符优先级高于栈顶运算符,则将当前运算符压入栈。
      • 若当前运算符优先级低于或等于栈顶运算符,从栈中弹出运算符并添加到数组,直到栈顶运算符优先级低于当前运算符,再将当前运算符压入栈。
    • 如果是左括号 (,直接压入栈。
    • 如果是右括号 ),则不断从栈中弹出运算符并添加到数组,直到遇到对应的左括号,然后将这对括号丢弃。
  3. 处理剩余运算符:当所有字符被处理后,将栈中剩余的运算符依次弹出并添加到数组。

后缀表达式的计算

计算后缀表达式相对直接,主要步骤如下:

  1. 初始化:创建一个空的数值栈。
  2. 遍历后缀表达式:逐个检查后缀表达式中的每个元素。
    • 如果是操作数,将其压入数值栈。
    • 如果是运算符,从数值栈中弹出两个操作数,执行相应的运算,然后将结果压回栈中。
  3. 最终结果:当所有元素被处理后,数值栈的顶部元素就是整个表达式的计算结果。

示例

假设我们要计算中缀表达式 (3 + 4) * 5 的值,我们首先将其转换为后缀表达式:

  1. 初始化:[](运算符栈) [](输出队列)
  2. 遍历:
    • (: 压栈 [(]
    • 3: 输出 [3]
    • +: 压栈 [( +]
    • 4: 输出 [3 4]
    • ): 弹出 + 并输出 [3 4 +]
    • *: 压栈 [ *]
    • 5: 输出 [3 4 + 5]
    • EOL: 弹出 * 并输出 [3 4 + 5 *]
  3. 结果:3 4 + 5 *

接下来,我们计算这个后缀表达式:

  1. 初始化:[](数值栈)
  2. 遍历:
    • 3: 压栈 [3]
    • 4: 压栈 [3 4]
    • +: 弹出 4 和 3,计算 3 + 4 = 7,压栈 [7]
    • 5: 压栈 [7 5]
    • *: 弹出 5 和 7,计算 7 * 5 = 35,压栈 [35]
  3. 结果:35

因此,中缀表达式 (3 + 4) * 5 的值为 35

结论

后缀表达式的转换和计算提供了一种高效且无歧义的方式来处理算数表达式。这种方法不仅在理论上有趣,在实际应用中也非常实用,特别是在需要快速准确地计算表达式的场景中。

下面是我的代码部分

中缀转后缀利用的链栈,所以先加入链栈头文件。

LinkStack.h

#pragma once
#include<stdio.h>
#include<stdlib.h>typedef char DataType;typedef struct LStackNode
{DataType data;struct LStackNode* next;
}LStackNode,*LinkStack;void InitLinkStack(LinkStack* top);int StackEmpty(LinkStack top);int PushStack(LinkStack top, DataType e);int PopStack(LinkStack top, DataType* e);int GetTop(LinkStack top, DataType* e);int StackLength(LinkStack top);void DestoryStack(LinkStack top);

LinkStack.cpp

#include "LinkStack.h"
#define _CRT_SECURE_NO_WARNINGS 1void InitLinkStack(LinkStack* top)
{*top = (LinkStack)malloc(sizeof(LStackNode));(*top)->next = NULL;//将链栈头结点指针域置为空
}int StackEmpty(LinkStack top)
{//判断链栈是否为空if (top->next == NULL){return 1;}else{return 0;}
}int PushStack(LinkStack top, DataType e)
{//将元素e入栈,入栈成功返回1LStackNode* p;p = (LStackNode*)malloc(sizeof(LStackNode));p->data = e;p->next = top->next;top->next = p;return 1;
}int PopStack(LinkStack top, DataType* e)
{//将栈顶元素出栈LStackNode* p;p = top->next;if (!p){printf("栈已空\n");return 0;}*e = p->data;top->next = p->next;free(p);return 1;
}int GetTop(LinkStack top, DataType* e)
{//取栈顶元素LStackNode* p;p = top->next;if (!p){printf("栈已空\n");return 0;}*e = p->data;return 1;
}int StackLength(LinkStack top)
{//求链栈长度LStackNode* p;int count = 0;p = top;while (p->next){p = p->next;count++;}return count;
}void DestoryStack(LinkStack top)
{//销毁链栈LStackNode* p, * q;p = top;while (!p){q = p;p = p->next;free(q);}
}

主函数

#define _CRT_SECURE_NO_WARNINGS 1
#include"LinkStack.h"
#include<stdio.h>constexpr auto MaxSize = 50;;typedef struct
{float data[MaxSize];int top;
}SeqStack;void TranslateExpress(char str[], char exp[]);
float ComputeExpress(char exp[]);int main()
{char a[MaxSize], b[MaxSize];float f;printf("请输入一个算数表达式:\n");gets_s(a);printf("中缀表达式为:%s\n", a);TranslateExpress(a,b);printf("后缀表达式为:%s\n",b);f = ComputeExpress(b);printf("计算结果:%.0f\n", f);return 0;
}void TranslateExpress(char str[], char exp[])
{//中缀表达式转后缀表达式LinkStack S;//定义一个栈,用于存放运算符InitLinkStack(&S);char ch;char e;int i = 0, j = 0;ch = str[i];i++;while (ch != '\0')//依次扫描中缀表达式中的每个字符{switch (ch){case '('://如果当前字符是左括号,则将其入栈PushStack(S, ch);break;case ')'://如果是右括号,则将栈中的运算符出栈,并将其存入数组exp中while (GetTop(S, &e) && e != '('){PopStack(S, &e);exp[j] = e;j++;exp[j] = ' ';j++;}PopStack(S, &e);//将左括号出栈break;case '+'://如果遇到+和-,因为其优先级低于栈顶运算符的优先级,case '-'://所以先将栈顶运算符出栈,并将其存入数组exp中,然后将当前运算符入栈while (!StackEmpty(S) && GetTop(S, &e) && e != '('){PopStack(S, &e);exp[j] = e;j++;exp[j] = ' ';j++;}PushStack(S, ch);//将当前运算符入栈break;case '*'://如果遇到的是*和/,则先将同级运算符出栈,并存入数组exp中,case '/'://然后将当前运算符出栈while (!StackEmpty(S) && GetTop(S, &e) && e == '/' || e == '*'){PopStack(S, &e);exp[j] = e;j++;exp[j] = ' ';j++;}PushStack(S, ch);//当前运算符入栈break;case ' '://如果遇到空格,忽略break;default://如果遇到操作数,则将操作数直接送入数组exp中,并在//其后添加一个空格,用来分割数字字符while (ch >= '0' && ch <= '9'){exp[j] = ch;j++;ch = str[i];i++;}i--;exp[j] = ' ';j++;break;}ch = str[i];//读入下一个字符,准备处理i++;}while (!StackEmpty(S))//将栈中所有剩余的运算符出栈,送入数组exp中{PopStack(S, &e);exp[j] = e;j++;exp[j] = ' ';j++;}exp[j]='\0';
}float ComputeExpress(char exp[])
{//计算后缀表达式的值SeqStack S;//利用顺序栈存储浮点型数据,从而防止与链栈中存储的S.top = -1;//字符数据冲突,利用C++中的模板能更好的解决这一点int i = 0, value;float x1, x2;float result;while (exp[i] != '\0'){if (exp[i] != ' ' && exp[i] >= '0' && exp[i] <= '9'){//如果当前字符是数字字符value = 0;while (exp[i] != ' '){value = value * 10 + exp[i] - '0';i++;}S.top++;S.data[S.top] = value;//处理之后将数字入栈}else//如果当前字符是运算符{switch (exp[i]){//将栈中的数字出栈两次,然后用当前的运算符进行运算,再将结果入栈case '+':x1 = S.data[S.top];S.top--;x2 = S.data[S.top];S.top--;result = x2 + x1;S.top++;S.data[S.top] = result;break;case '-':x1 = S.data[S.top];S.top--;x2 = S.data[S.top];S.top--;result = x2 - x1;S.top++;S.data[S.top] = result;break;case '*':x1 = S.data[S.top];S.top--;x2 = S.data[S.top];S.top--;result = x2 * x1;S.top++;S.data[S.top] = result;break;case '/':x1 = S.data[S.top];S.top--;x2 = S.data[S.top];S.top--;result = x2 / x1;S.top++;S.data[S.top] = result;break;}i++;}}result = S.data[S.top];S.top--;if (S.top == -1)//如果栈不为空,则将结果出栈,并返回{return result;}else{printf("表达式错误\n");exit(-1);}
}

反思总结:

        当时写的时候看着参考书的代码一直有个疑问,为什么中缀转后缀用的链栈,而计算后缀的时候用的顺序栈,为什么不能直接都用链栈呢,通过一番折腾,我知道了,因为我的参考书是一本C语言的数据结构,在LinkStack.h里面我规定了DataType是char类型,中缀转后缀,链栈里面存储的是char类型,但是要是计算后缀的话就不能再调用链栈这个数据结构了,因为计算后缀要将数值入栈,是float类型,此时由于我写的链栈只能存储char类型而导致编译器报错,解决这个问题我查阅资料找到了多种办法:一,由于C语言的限制,我可以再写一个存储数值的链栈数据结构,这样较麻烦,得重写链栈操作文件;二,可以利用指针函数,这个又要大幅度改动我的LinkStack.cpp文件里面的函数传参部分,我也没选这种方法;三,可以在main函数里面利用另一种数据结构顺序栈,这是我觉得更方便的办法,虽然会导致部分内存空间的浪费,但只需要简单的改动栈顶指针进行入栈和出栈操作即可。

        另外,如果是C++语言将更好的解决这一问题,C++引入了模板,可以在一个程序里利用存储不同数据类型的数据结构,我对模板的应用不太深入,C++是大一上学期自学的,现在已经忘了大部分,所以返回去深入学习的一下模板的应用,针对本题在C++中直接定义LinkStack<char> charStack和LinkStack<float> floatStack可以完美的解决这一问题。

        希望这篇博客能帮助你更好地理解后缀表达式的转换和计算过程,以及它在算数表达式求值中的重要性。如果你有任何疑问或需要进一步的解释,请随时提问!

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

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

相关文章

PT2262-IR

PT2262是一款很古老的编码芯片&#xff0c;其兼容型号有&#xff1a;SC2262&#xff0c;AD2262&#xff0c;SC2260(需改变匹配电阻)等。 依据其datasheet&#xff0c;PT2262射频模式工作原理: CODE BITS A Code Bit is the basic component of the encoded waveform, and ca…

34_YOLOv5网络详解

1.1 简介 YOLOV5是YOLO&#xff08;You Only Look Once&#xff09;系列目标检测模型的一个重要版本&#xff0c;由 Ultralytics 公司的Glenn Jocher开发并维护。YOLO系列以其快速、准确的目标检测能力而闻名&#xff0c;尤其适合实时应用。YOLOV5在保持高效的同时&#xff0c…

LeetCode/NowCoder-二叉树OJ练习

励志冰檗&#xff1a;形容在清苦的生活环境中激励自己的意志。&#x1f493;&#x1f493;&#x1f493; 目录 说在前面 题目一&#xff1a;单值二叉树 题目二&#xff1a;相同的树 题目三&#xff1a;对称二叉树 题目四&#xff1a;二叉树的前序遍历 题目五&#xff1a;另…

鸿蒙OpenHarmony Native API【drawing_path.h】 头文件

drawing_path.h Overview Related Modules: [Drawing] Description: 文件中定义了与自定义路径相关的功能函数 Since: 8 Version: 1.0 Summary Functions FunctionDescription[OH_Drawing_PathCreate] (void)[OH_Drawing_Path] * 函数用于创建一个路径对象OH_Drawin…

蜂窝物联云平台:一站式服务,智能生活从此开始!

蜂窝云平台 一、PC端展示与管理 GIS地图整合 在GIS地图上精确展示地块&#xff0c;轻松点选查看详细设备信息、实时监控和控制功能&#xff0c;以及基地的全方位介绍。 个性化定制界面 界面布局与功能展示均可按需求定制&#xff0c;打造独一无二的用户体验。 数据集中看板 将…

【Python机器学习】k-近邻算法简单实践——改进约会网站的配对效果

需求背景&#xff1a; XX一直使用约会网站寻找适合自己的约会对象&#xff0c;ta会把人分为3种类型&#xff1a; 不喜欢、魅力一般、非常有魅力 对人分类轴&#xff0c;发现了对象样本的以下3种特征&#xff1a; 1、每年获得的飞行里程数 2、玩视频游戏所耗时间百分比 3、…

linux操作系统之线程

1.线程概念 线程是一个轻量级进程,每一个线程都属于一个进程 进程是操作系统资源分配的最小单位,而线程是CPU任务调度的最小单位 线程是一个任务执行的过程,包括创建,调度,消亡 创建:线程空间位于进程空间,进程中的线程,栈区独立,并共享进程中的数据区,文本区,堆区 调度:宏观…

常见的JS混淆及处理办法

1&#xff0c;变量名混淆 文本增添属性的过程中有很多操作空间 原始代码&#xff1a; s[age,job] function xx(){};xx.prototype[s[0]]15 xx.prototype[s[1]]teacheranew xx() 将属性名经过base64加密&#xff0c;并对函数名xx,&#xff0c;数组名s&#xff0c;经过混淆处理…

【网络安全的神秘世界】 文件上传及验证绕过

&#x1f31d;博客主页&#xff1a;泥菩萨 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 | 每天学会一个渗透测试工具 这个漏洞对于初学者好挖&#xff0c;先找到文件上传的位置 文件上传是web网页中常见的功能之一&#xff0c;通常情况下恶意文…

【数据结构初阶】一篇文章带你超深度理解【单链表】

hi &#xff01; 目录 前言&#xff1a; 1、链表的概念和结构 2、单链表&#xff08;Single List&#xff0c;简写SList&#xff09;的实现 2.1 定义链表&#xff08;结点&#xff09;的结构 2.2 创建一个链表 2.3 打印链表 2.4 尾插 2.5 头插 2.6 尾删 2.7 头…

python中argparse模块及action=‘store_true‘详解

1. 指定action时 通俗讲&#xff0c;action的作用就是在命令行中指定参数名称时&#xff0c;参数的取值。 如&#xff1a; parser.add_argument(--save-file, actionstore_true, defaultFalse, help是否保存文件) 给参数设置action之后&#xff0c;命令执行时&#xff0c;…

【BUG】已解决:You are using pip version 10.0.1, however version 21.3.1 is available.

You are using pip version 10.0.1, however version 21.3.1 is available. 目录 You are using pip version 10.0.1, however version 21.3.1 is available. 【常见模块错误】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#…

数据结构初阶(C语言)-二叉树

一&#xff0c;树的概念与结构 树是⼀种非线性的数据结构&#xff0c;它是由 n&#xff08;n>0&#xff09; 个有限结点组成⼀个具有层次关系的集合。把它叫做树是因为它看起来像⼀棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 1.有⼀个特殊的结点&a…

【Linux学习 | 第1篇】Linux介绍+安装

文章目录 Linux1. Linux简介1.1 不同操作系统1.2 Linux系统版本 2. Linux安装2.1 安装方式2.2 网卡设置2.3 安装SSH连接工具2.4 Linux和Windows目录结构对比 Linux 1. Linux简介 1.1 不同操作系统 桌面操作系统 Windows (用户数量最多)MacOS ( 操作体验好&#xff0c;办公人…

昇思25天学习打卡营第22天|基于MindNLP+MusicGen生成自己的个性化音乐

文章目录 昇思MindSpore应用实践1、MusicGen模型简介残差矢量量化&#xff08;RVQ&#xff09;SoundStreamEncodec 2、生成音乐无提示生成文本提示生成音频提示生成 Reference 昇思MindSpore应用实践 本系列文章主要用于记录昇思25天学习打卡营的学习心得。 1、MusicGen模型简…

python使用 tkinter 生成随机颜色

先看效果: 只要不停点击底部的按钮&#xff0c;每次都会生成新的颜色。炫酷啊。 import random import tkinter import tkinter.messagebox from tkinter import Button# todo """ 1. 设置一个按钮&#xff0c;来让用户选择是否显示颜色值 2. 把按钮换成 Label…

谷粒商城实战笔记-错误记录-启动失败

文章目录 一&#xff0c;lombok报错二&#xff0c;Output directory is not specified 一&#xff0c;lombok报错 java: You arent using a compiler supported by lombok, so lombok will not work and has been disabled. Your processor is: com.sun.proxy.$Proxy8 Lombok …

初试Ollama本地大模型

准备工作 机器配置&#xff1a; CPUi5-10400内存16GB硬盘SSD 480GB显卡GTX 1660 系统&#xff1a;Ubuntu 18.04 Server NVIDIA驱动安装 - 下载 驱动下载地址&#xff1a;https://www.nvidia.cn/geforce/drivers/ - 获取下载链接 GTX 1660驱动下载链接&#xff1a;https://…

怎么理解FPGA的查找表与CPLD的乘积项

怎么理解 fpga的查找表 与cpld的乘积项 FPGA&#xff08;现场可编程门阵列&#xff09;和CPLD&#xff08;复杂可编程逻辑器件&#xff09;是两种常见的数字逻辑器件&#xff0c;它们在内部架构和工作原理上有着一些显著的区别。理解FPGA的查找表&#xff08;LUT&#xff0c;L…

在 Android 上实现语音命令识别:详细指南

在 Android 上实现语音命令识别:详细指南 语音命令识别在现代 Android 应用中变得越来越普遍。它允许用户通过自然语言与设备进行交互,从而提升用户体验。本文将详细介绍如何在 Android 上实现语音命令识别,包括基本实现、带有占位槽位的命令处理,以及相关的配置和调试步骤…