中缀表达式求值(利用双栈,扩展到小数,括号匹配问题,不以’@'作为结尾)
#include<iostream>
#include<string>
#include"SeqStack.h"
using namespace std;
//打表,记录算符间的优先关系
char a[7][7] = {
{'>','>','<','<','<','>','>'},
{'>','>','<','<','<','>','>'},
{'>','>','>','>','<','>','>'},
{'>','>','>','>','<','>','>'},
{'<','<','<','<','<','=',' '},
{'>','>','>','>',' ','>','>'},
{'<','<','<','<','<',' ','='}
};
int optrtoindex(char ch) {//将操作符转换成行标和列标
if (ch == '+') return 0;
if (ch == '-') return 1;
if (ch == '*') return 2;
if (ch == '/') return 3;
if (ch == '(') return 4;
if (ch == ')') return 5;
if (ch == '@') return 6;
}
double Operator(double left, char op, double right) {//计算
if (op == '+') return left + right;
if (op == '-') return left - right;
if (op == '*') return left * right;
if (op == '/') {
if (right == 0.0) { cout << "分母不能为0!" << endl;exit(1); }
else return left / right;
}
}
//能扩展到小数,不用@,对括号匹配进行判断
double Expression_Eval2() {
SeqStack<char, 100>OPTR;//操作符栈
SeqStack<double, 100>OPND;//操作数栈
OPTR.push('@');//初始化操作符栈
char ch = getchar();
double ans=0;
int flag = 0;
int cnt = 0;//计数器,计数小数的位数
while (ch!='@' || OPTR.Top() != '@')//当读入的字符为'\n'并且栈顶的操作符也为@时,退出循环
{
if (ch >= '0' && ch <= '9' ) {
ans = ans * 10 + (ch - '0');
if (flag == 1) cnt++;//表示现在在读取小数
ch = getchar();
if (ch == '\n') ch = '@';
if (ch == '.') {
flag = 1;
ch = getchar();
if (ch == '\n') ch = '@';
}
if (ch < '0' || ch>'9')//读到操作符
{
ans = ans / pow(10, cnt);
OPND.push(ans);
ans = 0;
flag = 0;
cnt = 0;
}
}
else {
char pre_op = OPTR.Top();//获取栈顶的操作符,用于和当前读入的操作符比较优先关系
switch (a[optrtoindex(pre_op)][optrtoindex(ch)])//查表
{
case '>': {
char op = OPTR.pop();//操作符出栈
if (op != '+' && op != '-' && op != '*' && op != '/') {
cerr << "表达式有误!" << endl;
exit(1);
}
double right = OPND.pop();
double left = OPND.pop();//连续弹出两个操作数
//计算结果,并压入栈中
OPND.push(Operator(left, op, right));
break;
}
case '='://pop掉的其实是'('
{
if (OPTR.pop() == '(') {
ch = getchar();
if (ch == '\n') ch = '@';
}
else {
cerr << "括号不匹配" << endl;
exit(1);
}
break;
}
case '<':
{
OPTR.push(ch);
ch = getchar();
if (ch == '\n') ch = '@';
break;
}
case ' ':
{
cerr << "括号不匹配" << endl;
exit(1);
}
}
}
}
return OPND.Top();//计算完成,返回结果
}
int main()
{
// double res1=Expression_Eval1();
double res2 = Expression_Eval2();
// cout << res1 << endl;
cout << res2 << endl;
return 0;
}
- 扩展到多位数和小数:我们先扩展到多位数,再尝试扩展到小数:
多位数:先定义一个变量ans,存储多位数。在读到操作符之前,每读到一个数字字符,就利用ans=ans*10+ch-'0'
得到ans;一旦读到了操作符,就将ans压入栈中,并将ans恢复为0 。
小数:读到小数点之后开始计算在读到操作符之前的小数位数(cnt),一旦读到操作符,就将刚才求的多位数除以10的cnt次方,可以用pow(10,cnt)
实现。 - 不使用’@‘作为表达式结束符,使输入符合习惯:我们平时输入的习惯就是:输入一个正常的表达式,再按一个回车,就能得到结果,那我们就可以将回车作为表达式结束符,当读到的字符为’\n’时,让程序自动将’\n’这个字符转换成’@',这样我们就不用改动很多代码了。
- 括号匹配:读到的操作符和栈顶的操作符的关系有以下几种:
’>‘:此时运算符要出栈,与操作数进行运算。此时如果操作符不是合法操作符,那么就应该报相关错误信息。
’=‘:此时要pop栈顶,只有一种可能,pop出来的字符一定是’(‘,因为要和’)'匹配,那如果pop出的字符不是这个,那么就应该报相关错误信息。
‘<’:此时将字符压入栈中,不需要检查。
’ ':此时,无法比较字符间的关系,说明表达式有误,应该报相关错误信息。
中缀表达式转后缀表达式
- 利用栈(需要操作符栈)
扫描中缀表达式,当读入的是ch>=‘0’或ch<=‘9’的字符时,直接加入到集合中,如果读入的是操作符,则要先判断读入的操作符与栈顶操作符的优先级关系(初始时的栈有一个操作符’@’,方便操作)。如果是’>‘,则弹出栈顶元素到集合中,然后继续判断与栈顶元素的优先级关系,一旦关系变成了’>‘,则就将该操作符压入到栈中。如果出现’=',就将栈顶元素弹出(不加入到集合!),继续扫描表达式。
注意:1.后缀表达式中的每个元素(数字或者操作符)之间都需要用空格隔开,否则在读取表达式时可能会出现错误。
2.使用空格将每个操作数和操作符分隔开,便于读取和计算。
3.后缀表达式的最后一个元素不需要加空格。
比如:
1+5*(3+2)-4*5转成后缀表达式为:1 5 3 2 + * + 4 5 * -
2.注意事项:我在编写中缀表达式转换成后缀表达式时出现了以下问题:
- 当我输入一串表达式时,返回的结果却是一串乱码。询问chatgpt得知:在 C++ 中,字符串拼接运算符 + 只能用于连接两个字符串,不能将字符和字符串直接相加。因此,这里的 ch 是一个字符,而 " " 是一个字符串,所以会导致结果出现乱码。
- res += to_string(ch) + " ",当我改为这种写法的时候,出现了一堆数字:49 53 51 50 43 42 43 52 53 42 45,经观察,这些数字恰好对应着后缀表达式字符的ASCII码,利用res += string(1, ch) + " " 这个写法就能正确显示结果了。
- to_string() 和 string(1, ch) 的作用都是将字符转换为对应的字符串,具体区别如下:
to_string():将数字或字符等转换为字符串,可以用于多种类型转换,例如将整型、浮点数、枚举等类型转换为字符串。to_string(ch) 表示将 ch 转换为对应的字符串。
string(1, ch):将字符 ch 转换为一个只包含 ch 的字符串。前面的 1 表示字符串中的字符数量为 1。
所以,在这里我们使用 string(1, ch) 来将字符转换为字符串。因为 to_string() 主要用于数字类型转换,而 char 类型是不能直接作为参数传递给 to_string() 的,需要先将其转换为整型。而 string(1, ch) 在转换字符为字符串时更加简洁和高效。
所以在利用第一种写法时,ch先转换成了数字(ASCII),再将数字转换成了字符串。
3.完整代码:
//中缀转后缀(个位数)
string infixTopostfix() {
string res="";
SeqStack<char, 100>OPTR;//操作符栈
OPTR.push('@');//初始化操作符栈
char ch = getchar();
while (ch != '@' || OPTR.Top() != '@')//当读入的字符为'\n'并且栈顶的操作符也为@时,退出循环
{
if (ch >= '0' && ch <= '9') {
res += string(1, ch) + " ";//每个操作数用空格隔开
ch = getchar();
if (ch == '\n') ch = '@';
}
else {
char pre_op = OPTR.Top();//获取栈顶的操作符,用于和当前读入的操作符比较优先关系
switch (a[optrtoindex(pre_op)][optrtoindex(ch)])//查表
{
case '>': {
res += string(1,OPTR.pop()) + " ";
break;
}
case '=':
{
OPTR.pop();
ch = getchar();
if (ch == '\n') ch = '@';
break;
}
case '<':
{
OPTR.push(ch);//读到优先级高的操作符就进栈
ch = getchar();
if (ch == '\n') ch = '@';
}
}
}
}
return res;
}
该代码存在局限性,不能对小数或多位数进行操作。
后缀表达式求值(利用操作数栈)
1.扫描表达式,读到数字则压入到栈中,读到操作符则弹出栈顶的两个元素做运算,然后再把结果压入到栈中,然后继续扫描表达式。
2.完整代码为:
//后缀表达式求值
double postfix_Eval(string str)//传入表达式
{
SeqStack<double, 100>OPND;//操作数栈
for (int i = 0;i < str.size();)
{
if (str[i] >= '0' && str[i] <= '9')//数字,进栈
OPND.push(str[i] - '0'), i++;
else if (str[i] == ' ')//读到空格 i++
i++;
else {//读到操作符
double right = OPND.pop();
double left = OPND.pop();
OPND.push(Operator(left, str[i], right));
i++;
}
}
return OPND.Top();
}
总结:
求中缀表达式有两种做法,一种是利用双栈,直接求;第二种是先利用操作符栈将中缀表达式转换成后缀表达式,再利用操作数栈求出后缀表达式的值。两者本质上是相同的,都利用了两个栈。