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();
}