归纳编程学习的感悟,
记录奋斗路上的点滴,
希望能帮到一样刻苦的你!
如有不足欢迎指正!
共同学习交流!
🌎欢迎各位→点赞 👍+ 收藏⭐ + 留言📝
没人会嘲笑竭尽全力的人!
前言
在计算机科学中,算数表达式的求值是一个经典的问题,尤其是在编译器设计和计算器应用中。中缀表达式,即我们日常所见的数学表达式(如
3 + 4
或(2 + 3) * 4
),虽然直观易懂,但对于计算机来说并不友好,因为它们需要遵循特定的运算优先级规则。因此,将中缀表达式转换为后缀表达式(也称为逆波兰记法或RPN)可以简化计算过程,使计算机能够更有效地进行求值。
后缀表达式简介
后缀表达式的特点是运算符紧跟在其操作数之后,例如,中缀表达式 3 + 4
转换为后缀表达式后成为 3 4 +
。这种格式消除了对运算符优先级的依赖,也不需要使用括号来表示运算的顺序,这使得计算变得更为直接和高效。
中缀到后缀的转换
步骤概述
- 初始化:创建一个空的运算符栈和一个空的数组。
- 遍历输入:逐个检查中缀表达式中的每个字符。
- 如果是操作数,直接将其添加到数组。
- 如果是运算符,根据栈顶运算符的优先级与当前运算符比较:
- 若当前运算符优先级高于栈顶运算符,则将当前运算符压入栈。
- 若当前运算符优先级低于或等于栈顶运算符,从栈中弹出运算符并添加到数组,直到栈顶运算符优先级低于当前运算符,再将当前运算符压入栈。
- 如果是左括号
(
,直接压入栈。 - 如果是右括号
)
,则不断从栈中弹出运算符并添加到数组,直到遇到对应的左括号,然后将这对括号丢弃。
- 处理剩余运算符:当所有字符被处理后,将栈中剩余的运算符依次弹出并添加到数组。
后缀表达式的计算
计算后缀表达式相对直接,主要步骤如下:
- 初始化:创建一个空的数值栈。
- 遍历后缀表达式:逐个检查后缀表达式中的每个元素。
- 如果是操作数,将其压入数值栈。
- 如果是运算符,从数值栈中弹出两个操作数,执行相应的运算,然后将结果压回栈中。
- 最终结果:当所有元素被处理后,数值栈的顶部元素就是整个表达式的计算结果。
示例
假设我们要计算中缀表达式 (3 + 4) * 5
的值,我们首先将其转换为后缀表达式:
- 初始化:
[]
(运算符栈)[]
(输出队列) - 遍历:
(
: 压栈[(]
3
: 输出[3]
+
: 压栈[( +]
4
: 输出[3 4]
)
: 弹出+
并输出[3 4 +]
*
: 压栈[ *]
5
: 输出[3 4 + 5]
EOL
: 弹出*
并输出[3 4 + 5 *]
- 结果:
3 4 + 5 *
接下来,我们计算这个后缀表达式:
- 初始化:
[]
(数值栈) - 遍历:
3
: 压栈[3]
4
: 压栈[3 4]
+
: 弹出4
和3
,计算3 + 4 = 7
,压栈[7]
5
: 压栈[7 5]
*
: 弹出5
和7
,计算7 * 5 = 35
,压栈[35]
- 结果:
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可以完美的解决这一问题。
希望这篇博客能帮助你更好地理解后缀表达式的转换和计算过程,以及它在算数表达式求值中的重要性。如果你有任何疑问或需要进一步的解释,请随时提问!