数据结构——表达式求值

1.表达式的介绍

(1)表达式的组成:

表达式分为三个部分,操作数运算符界限符

操作数就是数字,运算符就是加减乘除一类的运算符号,界限符就是我们熟悉的大括号、中括号和小括号。界限符是决定运算顺序的符号。

运算符+-*/,+-为相同优先级,*/为相同优先级,*/大于+-的优先级。

另外,没有必要在计算机中也加入大括号、中括号的概念,那样只会复杂化,因为实际上大括号和中括号起的作用是和小括号一样的。

大括号和中括号只是给人一种更加美观,便于成对识别的一种方法。

(2)三种表达式:

表达式分为中缀表达式,后缀表达式和前缀表达式。

前缀表达式又叫波兰式,而后缀表达式又叫逆波兰式,因为一开始研究这个的是个波兰的数学家。

其中我们最熟悉的就是中缀表达式。如下:

(15-2)*3+4/2

简单表达式

对于五加三的简单表达式,三种表达式有三种不同的表达方式,而结果其实并没有变化。

中缀表达式就是将运算符置于两个操作数之间,如5+3。

后缀表达式就是将运算符置于两个操作数之后,如5 3+。

前缀表达式就是将运算符置于两个操作数之前,如+5 3。

注意:这里数字的顺序是不能颠倒的,尤其对于-*/这三种运算符,5-3跟3-5的结果是不一样的。

对于上面的5相同的地位的数称为前操作数,对于上面的3相同的地位的数称为后操作数。

复合表达式

我们知道一个表达式最终目的是化为一个操作数。因此,我们就可以在复合表达式中,将某个部分表达式视为一个操作数。

比如说( 15 - 5 ) / 2 + 6 / 2,我们就可以将( 15 - 5 ) / 2当成一个操作数去对待,而不用管表达式里面如何如何复杂。然后按运算顺序将符合表达式简化成一个简单的表达式5+3,最后得出结果。

中缀表达式的计算:

其实,有的中缀表达式,不止一个运算顺序。

如:15/5*3+5-2

我们既可以先算乘法,再算除法,也可以先算除法,再算乘法。这是运算的结合律。

在上面的式子乘除运算之后,我们既可以先算加法,也可以先算减法。

然而对于算法来说,算法具有确定性,因此计算机一般转换时都是按‘左优先’算法转换的,即假如运算符的优先级相同,比如*和/这两种运算符,谁在左边先计算谁。也就是说能先从左边开始,就从左边开始。

然而中缀表达式,对于人来说,是非常好计算的。但对于计算机而言,中缀表达式的计算方法是非常复杂的。

这就需要先将中缀转前缀或者后缀,然后按转换后的表达式的计算方法计算。

2.实现

这里只是为了理清表达式的知识,因此下面的代码并没有考虑到操作数为负数的情况。

(1)将中缀表达式转换为后缀表达式

手算方法:

 举个例子:

(15-2)*3+4/2将这个中缀表示式化为后缀表达式。

我们先确定运算的顺序,如下:

第一步:15 2 -,我们将这个部分表达式视为一个操作数,进行下一步。

第二步:15 2 - 3 *,操作数顺序不能颠倒。同理视为操作数,进行下一步。

第三步: 15 2 - 3 * 4 2 /,这里又增加了一个‘操作数’,进行下一步。

第四步: 15 2 - 3 * 4 2 / +,最终的到的式子就是后缀表达式。

对于确立运算顺序的中缀表达式,转化后的后缀表达式是唯一的,也可以观察到上面我们求后缀表达式的式子,原本的运算符先后顺序为1243,转换为后缀表达式后为1234。

从上面可以得出后缀表达式的三个特点:

1.后缀表达式没有了中缀表达式表现运算顺序的括号。

2.操作数的先后顺序是和中缀表达式是相同的。

3.在后缀表达式中运算符的先后顺序决定了整个表达式的运算顺序。

其实后缀表达式还有一个特别重要的特点:

当中缀表达式中前一个运算符的优先级<后一个运算符的优先级时,在后缀表达式中这两个运算符的先后顺序颠倒,注意前提是这两个运算符相邻。

这一点可以读者自己证明。

代码实现:

上面的步骤可能不知道是怎么来的,下面是我自己的思考:

咱们先假设这么一个表达式,这个表达式没有括号。

我们设立一个栈,如果前后相邻的运算符,后一个运算符的优先级大于前一个运算符的优先级,直接将后一个运算符压栈。那么当然前一个运算符也在栈上,而且就在刚才压栈的运算符的下面。

如果小于等于前一个运算符的优先级,则不断出栈,并依次加在后缀表达式后面。

需要注意的是如果栈中有一个运算符,而中缀表达式已经遍历完了,则不能根据上面的情况出栈,这时需要自己出栈。

例如:

(1)5 - 4 / 2

运行到‘ / ’时,后缀表达式为5 4,栈从栈底到栈顶分别为-。

但此时,/的右操作数2还没加在后缀表达式上,因此需要将/压栈。

(2)5 - 4 + 2 

运行到‘ + ’时,后缀表达式为5 4,栈从从栈底到栈顶为-,‘ + ’和‘ - ’优先级相同,那么需要将‘ - ’出栈并加到后缀表达式的后面。并将’ + ‘压栈。

(3)5 * 4 - 2

运行到‘ - ’时,后缀表达式为5 4,‘ - ’优先级小于‘ * ‘, 那么需要将‘ * ’出栈加到后缀表达式的后面。并将’ - ‘压栈。

(4)5 * 4 / 2

运行到末尾时,后缀表达式为5 4 * 2 ,这时需要自己将/出栈并加到表达式后面。

现在考虑上括号,当我们遇到一个左括号立马将左括号压入栈中,遇到右括号时,只需要将栈中的符号依次加在表达式的后面即可。

这样可以消去括号,达到一个表达式转化为一个值的目的。

代码: 

void inToPo()
{
    //inExpression是中缀表达式
    //readNum(0,i)函数会将字符串0从i往后的完整的一个数字string返回
    //字符串0表示的是中缀表达式
    //且调用该函数会将i值变为数字最后一位的位置
    //h存放运算符号
	std::stack<char> h;
	for (int i = 0; i < inExpression.size(); i++)
	{
        //判断是否是数字
		if (isdigit(inExpression[i]))
		{
			poExpression += readNum(0,i);
            //加个空格纯是为了美观一点,下面这一句可以删掉,下面同理
			poExpression += ' ';
		}
		else if (inExpression[i] == '(')
			h.push(inExpression[i]);
		else if (inExpression[i] == ')')
		{
            //这一步骤会将中缀表达式的所有括号去掉
			while (h.top() != '(')
			{
				poExpression += h.top();
				poExpression += ' ';
				h.pop();
			}
			h.pop();
		}
		else if (isOp(inExpression[i]))
		{
			//isOp函数表示如果参数是+-*/中的一个返回true,否则返回false
            //priority函数返回int值,如果是+-就是0
            //如果是*/就是2,'('返回-1
			while (!h.empty() && priority(inExpression[i]) <= priority(h.top()))
			{
				poExpression += h.top();
				poExpression += ' ';
				h.pop();
			}
			h.push(inExpression[i]);
		}
	}
    //想象5+2*3,这是后缀表达式位5 2 3,h里面还有* 和 +
    //这时需要将h出栈,并依次放到表达式中5 2 3 * +
	while (!h.empty())
	{
		poExpression += h.top();
		poExpression += ' ';
		h.pop();
	}
}

(2)后缀表达式的计算 

对于上面我们求出的后缀表达式,如果我们想要计算,观察这个式子,很快就能想出计算方法。

方法如下:

比如说:上面我们求后缀表达式的中缀表达式式子为(15-2)*3+4/2,经计算为41。

后缀表达式为15 2 - 3 * 4 2 / +

我们采用后缀表达式的计算方法计算一遍。

第一次遇到运算符为‘-’,那么得到15-2=13。

第二次遇到运算符为‘*’,那么得到13*3=39。

第三次遇到运算符为‘/’,那么得到4/2=2。

第四次遇到运算符为‘+’,那么得到39+2=41。

完全和中缀表达式计算得到的结果相同。上面的方法对于哪一种后缀表达式都是适用的。

有了这种思想其实前缀表达式,同样的自己也就可以解决了。

(3)将中缀表达式转换为前缀表达式

手算方法:

举个例子:

(15-2)*3+4/2将这个中缀表示式化为前缀表达式。

我们先确定运算的顺序,如下:

第一步:/ 4 2,这里的表达式又是一个操作数。

第二步:- 15 2 / 4 2,操作数顺序不能颠倒

第三步:* - 15 2 3 / 4 2

第四步:+ * - 15 2 3 / 4 2,最终的到的式子就是前缀表达式。

最后将运算符先后顺序2341转化后为4321。

当然,前缀表达式也有一些特点,读者自己总结。

代码实现这里就不给了,前缀表达式了解即可,而且通过上面的方法可以自己推出代码实现。

(4)前缀表达式的计算 

对于上面我们求出的前缀表达式,我们观察这个式子,很快就能相处计算方法。

它和后缀表达式的计算方法顺序相反,如下:

举个例子: 计算上面求得的前缀表达式+ * - 15 2 3 / 4 2

 从右向左:

第一次遇到运算符’/‘,得到4/2=2。

第二次遇到运算符’-‘,得到15-2=13

第三次遇到运算符’*‘,得到13*3=39

第四次遇到运算符’+‘,得到39+2=41

因此,41就是该前缀表达式的值。

代码在最后。

//expression.h
#pragma once
#include<string>
#include<stack>
#include<exception>
class expression
{
public:
	expression(const char* s);
	~expression() {};
	int answer()const;//表达式结果
private:
	std::string inExpression;//中缀表达式
	std::string poExpression;//后缀表达式
	std::string prExpression;//前缀表达式

private:
	int priority(char op)const;//运算符的优先级
	int cal(int lhs, char op, int rhs)const;//运算lhs op rhs表达式
	bool isOp(char op)const;//是否是四种运算符号+-*/
	std::string readNum(int i, int& loc)const;//从字符串i从loc位置读取数字
                                                //,i=0,表示中缀表达式,1后缀,2前缀
	void inToPo();//中缀转后缀
	void inToPr() {};//中缀转前缀
};

//expression.cpp
#include "expression.h"
expression::expression(const char* s)
{
	inExpression = s;
	inToPo();
	//inToPr();
}
int expression::priority(char op)const
{
	switch (op)
	{
	case '+':
	case '-':return 0;
	case '*':
	case '/':return 1;
	default:
		break;
	}
	return -1;
}
bool expression::isOp(char op)const
{
	if (op == '+' || op == '-' || op == '*' || op == '/')
		return true;
	return false;
}
int expression::cal(int lhs, char op,int rhs )const
{
	switch (op)
	{
	case '+':return lhs + rhs;
	case '-':return lhs - rhs;
	case '*':return lhs * rhs;
	case '/':return lhs / rhs;
	default:
		break;
	}
	return 0;
}
std::string expression::readNum(int i, int& loc)const
{
	std::string obj;
	if (i == 0)
		obj = inExpression;
	else if (i == 1)
		obj = poExpression;
	else if (i == 2)
		obj = prExpression;
	else
		throw std::exception();
	int from = loc+1;
	while (isdigit(obj[from]))
	{
		from++;
	}
	int num = from - loc;
	int oldloc = loc;
	//注意这里的loc是最后一位数字的位置
	loc = from-1;
	return obj.substr(oldloc,num);
}
void expression::inToPo()
{
	//(15-2)*3+4/2
	std::stack<char> h;
	for (int i = 0; i < inExpression.size(); i++)
	{
		if (isdigit(inExpression[i]))
		{
			poExpression += readNum(0,i);
			poExpression += ' ';
		}
		else if (inExpression[i] == '(')
			h.push(inExpression[i]);
		else if (inExpression[i] == ')')
		{
			while (h.top() != '(')
			{
				poExpression += h.top();
				poExpression += ' ';
				h.pop();
			}
			h.pop();
		}
		else if (isOp(inExpression[i]))
		{
			//因为栈顶一定是优先级最高的,每次只需要和栈顶元素比较就行
			while (!h.empty() && priority(inExpression[i]) <= priority(h.top()))
			{
				poExpression += h.top();
				poExpression += ' ';
				h.pop();
			}
			h.push(inExpression[i]);
		}
	}
	while (!h.empty())
	{
		poExpression += h.top();
		poExpression += ' ';
		h.pop();
	}
}
int expression::answer()const
{
	std::stack<int>num;
	for (int i = 0; i < poExpression.size(); i++)
	{
		if (isdigit(poExpression[i]))
		{
			num.push(atoi(readNum(1,i).c_str()));
			//因为for会自动将i加一,下次循环在数字之后的一位进行
		}
		else if (poExpression[i] == ' ')
			continue;
		else if (isOp(poExpression[i]))
		{
			//注意栈第二个出栈的数才是前操作数
			int rhs = num.top();
			num.pop();
			int lhs = num.top();
			num.pop();
			num.push(cal(lhs, poExpression[i],rhs ));
		}
	}
	return num.top();
}

要实现表达式求值,可以使用栈这种数据结构。具体的实现步骤如下: 1. 定义两个栈,一个存储操作数,一个存储操作符。 2. 遍历表达式中的每个字符,根据字符的类型进行不同的处理: - 如果是数字,将其转换成数字,并压入操作数栈中。 - 如果是操作符,将其与操作符栈中的栈顶元素进行比较,如果当前操作符的优先级比栈顶元素的优先级高,则将当前操作符压入栈中;否则将操作符栈中的元素弹出,并将操作数栈中的两个操作数弹出进行运算,并将结果压入操作数栈中,直到当前操作符的优先级比栈顶元素的优先级高为止。 3. 遍历完表达式后,如果操作符栈中还有元素,将其依次弹出并进行运算,并将结果压入操作数栈中,直到操作符栈为空。 4. 最终操作数栈中的栈顶元素即为表达式的结果。 至于如何在 easyX 中实现这个过程,可以将表达式作为字符串输入,使用 C++ 标准库中的栈来实现操作数栈和操作符栈,具体的代码实现可以参考以下示例代码: ```c++ #include <iostream> #include <stack> #include <string> using namespace std; int priority(char op) { switch (op) { case '+': case '-': return 1; case '*': case '/': return 2; case '(': case ')': return 0; default: return -1; } } double calculate(double a, double b, char op) { switch (op) { case '+': return a + b; case '-': return a - b; case '*': return a * b; case '/': return a / b; default: return 0; } } double evaluate(string expr) { stack<double> nums; stack<char> ops; for (int i = 0; i < expr.length(); i++) { char ch = expr[i]; if (isdigit(ch)) { double num = 0; while (i < expr.length() && isdigit(expr[i])) { num = num * 10 + (expr[i] - '0'); i++; } i--; nums.push(num); } else if (ch == '(') { ops.push(ch); } else if (ch == ')') { while (!ops.empty() && ops.top() != '(') { char op = ops.top(); ops.pop(); double b = nums.top(); nums.pop(); double a = nums.top(); nums.pop(); nums.push(calculate(a, b, op)); } ops.pop(); } else { while (!ops.empty() && priority(ops.top()) >= priority(ch)) { char op = ops.top(); ops.pop(); double b = nums.top(); nums.pop(); double a = nums.top(); nums.pop(); nums.push(calculate(a, b, op)); } ops.push(ch); } } while (!ops.empty()) { char op = ops.top(); ops.pop(); double b = nums.top(); nums.pop(); double a = nums.top(); nums.pop(); nums.push(calculate(a, b, op)); } return nums.top(); } int main() { string expr = "2*(3+4)/5-6"; double result = evaluate(expr); cout << result << endl; return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值