栈的应用:中缀表达式求值、中缀转后缀、后缀表达式求值

中缀表达式求值(利用双栈,扩展到小数,括号匹配问题,不以’@'作为结尾)

#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出的字符不是这个,那么就应该报相关错误信息。
    ‘<’:此时将字符压入栈中,不需要检查。
    ’ ':此时,无法比较字符间的关系,说明表达式有误,应该报相关错误信息。

中缀表达式转后缀表达式

  1. 利用栈(需要操作符栈)
    扫描中缀表达式,当读入的是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();
}

总结:

求中缀表达式有两种做法,一种是利用双栈,直接求;第二种是先利用操作符栈将中缀表达式转换成后缀表达式,再利用操作数栈求出后缀表达式的值。两者本质上是相同的,都利用了两个栈。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值