目录
一.问题分析
将用中缀式表示的算术表达式转换为用逆波兰式(后缀式)表示的算术表达式,并计算用逆波兰式来表示的算术表达式的值.
如输入如下: 21+(( 42-2) *15+6 ) -18#
输出为:
原来表达式: 21+ (( 42-2 ) *15+6 ) - 18#
后缀表达式: 21&42&2&-15&*6&++18&-
计算结果: 609
二.算法思想
1.关于分词器
将输入串的所有非空白元素添加到一个vector <string> 数组里
在输入串(string)里设置两个(广义上的)指针,一前一后指向非空白元素.遇到空白字符自动向后跳
前 \ 后 |
数字 |
字符 |
数字 |
temp+=input[cur] |
temp+= input[cur] token.push_back(temp) temp.clear() |
字符 |
temp+=input[cur] token.push_back(temp) temp.clear() 比如(5+…) |
假设++,--操作是不存在的 Ep:碰到(3+5)*3 temp+=input[cur] token.push_back(curString) cur.clear() |
2.关于逆波兰式分析器:
或阅读该文:逆波兰分析
三.实现代码
1.头文件 & 类视图
using namespace std;
#include<iostream>
#include<string>
#include<stack>
#include<vector>
#include<algorithm>
/*
逆波兰式不需要括号,当从中缀式转换为RPN时,遇到 ( 括号要暂存之后的运算符;遇到 ) 括号要将内部的运算符(在TempStack)挨个转移到finStack
逆波兰计算的是表达式,可以写一个简化的分词器函数(只分析数字,运算符,空格),
将各个长度不一(如"55"长度为2,"+"长度为1)的元素分别进入一个 vector<string> token;
这个函数无需分析词性.分析的工作在核心函数Analysis()中对token[i][0]判别完成,
Analysis()同时还要调用isSym()来进行判别工作
tempStack 总是将优先级高的运算符放在栈顶,压住优先级低的运算符.当转移到finStack时,优先级高的先转移
*/
class RPN
{
public:
RPN(string);
~RPN();
bool isLegal(); //检测输入串是否含有非法字符(不是运算符||操作数的字符)
bool isNum(char); //经过分词程序后形成的vector<string> token. input:token[i][0] 每个元素的第一个字符
void divideElement(); //分词器
int Prio(char); //判断运算符的优先级,数字越小,优先级越弱
void analysis(); //分析程序,将token->逆波兰式
void Print(); //打印整个逆波兰式
void Computing(); //根据逆波兰式计算解
float getRes(); //返回最终值
private:
stack<string>tempExp; //临时栈,记录临时存放的运算符
stack<string>finExp; //最终栈,操作数依序放入即可
vector<string>Result; //记录fin栈的结果
vector<string>token; //接收分词后的字符串
string input; //接收输入的字符串
float res; //接收计算逆波兰式的最终结果
};
2.预处理部分
包括判断是否数字,输入是否合法,将输入串分词为独立的元素,存入token 数组
/*
isLegal(): 检测是否存在非法字符,即除了数字和运算符之外的其他所有字符
input: 类数据成员 vector<string> Input
out: 存在非法字符,报错. (调用isLegal的函数会据此决定是否返回)
*/
bool RPN::isLegal()
{
bool flag = true;
for (size_t i = 0; i < input.size(); i++) //遍历输入串的每一个char
{
if ((input[i] >= '0') && (input[i] <= '9')) //判断数字
{
continue;
}
else //判断运算符
{
if (input[i] != '+' || input[i] != '-' || input[i] != '*' || input[i] != '/' ||
input[i] != '(' || input[i] != ')' || input[i] != '#' || input[i] != ' ')
{
flag = false;
}
}
}
return flag;;
}
/*
isSym(): 经过isLegal() 处理后,只剩下数字,运算符,空格
return true: 是运算符
return false: 是数字
*/
bool RPN::isNum(char ch)
{
if (ch>='0' &&ch<='9')
{
return true;
}
else
{
return false;
}
}
/*
divideElement(): 将输入串分为一个个不含空格的元素,存储在一个vector<string>里
input: 成员变量:input
output: 成员变量: vector<string> token
*/
void RPN::divideElement()
{
size_t next = 1; //一开始,指向input[1]
string temp; //临时存放字符
for (size_t cur = 0; cur < input.size(); cur++,next++) //cur从[0]->[n-2]
{
if (next == input.size() - 1) { next--; }//当next到达最后一个,cur到达倒数第二个时.让next回退 1,可以有效阻止range溢出
if (input[cur] == ' ' || input[cur] == '\t') //跳过空格
{
cur++;
next = cur + 1; //next总指向cur下一个
}
if (input[next] == ' '|| input[next] == ' \t')
{
next++;
}
if (isNum(input[cur]))//第一个是数字
{
if (isNum(input[next])) //第二个是数字
{
temp += input[cur]; //将前一位添加到temp
if (cur == input.size() - 1) //当到达末尾时
{
token.push_back(temp);
temp.clear();
}
}
else //第二个是字符
{
temp += input[cur]; //前一个进入temp
token.push_back(temp); //token添加这个temp
temp.clear(); //清空temp,待留下次使用
}
}
else
{
temp += input[cur];
token.push_back(temp);
temp.clear();
}
}
}
3.逆波兰分析过程
包括优先级判断函数,以及核心的逆波兰分析程序
/*
Prio(): 对每个字符进行判别
input: token[i][0],即token每个元素的第一个字符
return: 该字符的优先级
*/
int RPN::Prio(char ch)
{
switch (ch)
{
case '#':return 0;
case'+':
case'-':return 1;
case'*':
case'/':
case'×':return 2;
case'(':
case')':return 3;
default:
break;
}
return 0;
}
/*
analysis(): 程序的核心部分,进行逆波兰式的转换
input(): token向量,temp栈,fin栈
return: finStack
*/
void RPN::analysis()
{
tempExp.push("#"); //先将优先级最低的"#"放在栈底
for (size_t i = 0; i < token.size(); i++) //遍历token
{
if (isNum(token[i][0])) //要么是数字
{
finExp.push(token[i]);
}
else //要么是运算符
{
if (token[i] == "(") //若x是'(',则直接压入temp
{
tempExp.push(token[i]);
}
else if (token[i] == ")") //若x是')',则将距离栈s1栈顶的最近的'('之间的运算符,逐个出栈,依次压入栈fin,
{
while (tempExp.top() != "(")
{
finExp.push(tempExp.top());
tempExp.pop();
}
tempExp.pop(); //此时抛弃 "("
}
else //此时不是( 与 )
{
if (tempExp.top()=="(") //此时temp栈顶是(,直接将扫描字符压入temp,因为()内是优先级最高的
{
tempExp.push(token[i]);
}
else //栈顶元素不为'('
{
if (Prio(token[i][0])>Prio(tempExp.top()[0])) //如果 token[i]的优先级 > temp栈顶的优先级,直接压入
{
tempExp.push(token[i]);
}
else
{
while (Prio(token[i][0]) <= Prio(tempExp.top()[0]) && tempExp.top()!="(") //否则,不断地将temp的栈顶转移到fin(不要括号),直到( temp的栈顶 的优先级 小于 token[i] 的优先级 或 栈顶为"("
{
finExp.push(tempExp.top());
tempExp.pop();
}
tempExp.push(token[i]);
}
}
}
}
}
//检查tempExp是否为空,否则依次转移到finExp
if (!tempExp.empty())
{
while (tempExp.top()!="#")
{
finExp.push(tempExp.top());
tempExp.pop();
}
}
}
4.计算,输出部分
包括将栈中的元素取出到一个vector,逆波兰式计算值
/*
Print: 打印结果
input: finExp栈
return: 一串cout结果
*/
void RPN::Print()
{
//倒腾数据结构
while (!finExp.empty())
{
Result.push_back(finExp.top());
finExp.pop();
}
//想要逆转过来
reverse(Result.begin(), Result.end());
//逆序打印
for (size_t i = 0; i <Result.size(); i++)
{
cout << Result[i] << " ";
}
cout << endl;
}
void RPN::Computing()
{
while (Result.size()!=1) //这样写比较耗费性能,容器物理上的增删需要花费额外的时间与内存.但是使用指针式的写法需要更多时间与精力,遂作罢
{
for (size_t i = 0; i < Result.size(); i++)
{
if (!isNum(Result[i][0])) //需要找到第一个运算符
{
float temp=0.0;
switch (Result[i][0])
{
case '+':
temp = stof(Result[i - 2]) + stof(Result[i - 1]);
//新值放在原运算符的位置上
break;
case '-':
temp = stof(Result[i - 2]) - stof(Result[i - 1]);break;
case '/':
temp = stof(Result[i - 2]) / stof(Result[i - 1]);break;
case'*':
case'×':
temp = stof(Result[i - 2]) * stof(Result[i - 1]);
break;
default:
break;
}
Result[i] = to_string(temp); //新值放在原运算符的位置上
Result.erase(Result.begin() + i - 2); //删除前两个操作数
Result.erase(Result.begin() + i - 2);
i = 0; //每次执行完后重新从首元素开始
}
}
}
res=stof(Result[0]);
}
float RPN::getRes()
{
return this->res;
}
5.mian()部分
int main()
{
string str="21+(( 42-2)*15+6 ) -18";
cout << "表达式: 21+(( 42-2)*15+6 ) -18\n";
RPN rpn(str);
//输入串判别
if (!rpn.isLegal())
{
cout << "含有非法字符,请重新输入\n";
//此处可以有一个goto语句
}
//分词
rpn.divideElement();
//分析
rpn.analysis();
//打印结果
rpn.Print();
//计算值
rpn.Computing();
cout<<rpn.getRes();
system("pause");
return 0;
}
四.总结
其实最后的计算值部分应该用传址的方式写的,但那样需要更多的时间与精力,时间上不允许优化效率了.