逆波兰(Reverse Polish Notation)四则运算表达式求值
最近在看大话数据结构这本书,感觉这本书确实很不错,深入浅出,算是算法入门难得的佳作!看得出作者写这本书也花了不少心血!
在第四章介绍栈之后介绍了栈的应用——四则表达式求值,但是没有代码,技痒,故花了一个晚上写了如下代码。
为了避免将来再看自己的程序看不懂是个啥思路,所以在代码里面做了充分的注释。
我看网上逆波兰的程序都是操作符和操作数各一个栈,然后一步到位的。鉴于初学,所以将逆波兰表达式和表达式计算分两个函数来写了。
栈在程序中的作用到底是什么?用书上的话能很好地回答这个问题:
1、将中缀表达式(标准的四则运算表达式)转换为后缀表达式(逆波兰表达式)(栈用来进出运算符号)
2、将后缀表达式进行计算得出结果(栈用来进出运算的操作数)
好吧,详情见代码了。如果发现代码有什么问题,还请不吝赐教。
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
//操作符链栈及其节点
typedef struct charNode
{
char data;
struct charNode * next;
}charNode, *charptr;
typedef struct charstack
{
charptr top;
int count;
}charstack;
bool Emptystack(charstack * S)
{
return (S->top == NULL);
}
void Push(charstack *S, char e)
{
charNode *one = new charNode;
one->data = e;
one->next = S->top;
S->top = one;
S->count ++ ;
}
bool Pop(charstack *S, char *e)
{
if (Emptystack(S))
return false;
*e = S->top->data;
charptr p = S->top;
S->top = S->top->next;
S->count --;
delete p;
return true;
}
//数字链栈及其节点
typedef struct doubleNode
{
double data;
struct doubleNode * next;
}doubleNode, *doubleptr;
typedef struct doublestack
{
doubleptr top;
int count;
}doublestack;
bool Emptystack(doublestack * S)
{
return (S->top == NULL);
}
void Push(doublestack *S, double e)
{
doubleNode *one = new doubleNode;
one->data = e;
one->next = S->top;
S->top = one;
S->count ++ ;
}
bool Pop(doublestack *S, double *e)
{
if (Emptystack(S))
return false;
*e = S->top->data;
doubleptr p = S->top;
S->top = S->top->next;
S->count --;
delete p;
return true;
}
// 将标准四则运算表达式(也即中缀运算表达式)转换成后缀运算表达式
string Reverse(string str)
{
string out = "";
string output = "";
charstack *cha = new charstack;
cha->count = 0;
cha->top = NULL;
int i = 0;
bool flag = false; // flag = false 表示数字是连续的,比如321是一个操作数;否则则认为中间有操作符
for(i=0; i<str.length(); i++)
{
if(str[i]>= '0' && str[i] <= '9')
{
if(flag == false)
out += str[i];
else
{
out += " ";
out += str[i];
flag = false; // flag 重新置成false
}
}
else if(str[i]=='.')
out += str[i]; //如果是小数点,直接输出即可
else if(str[i]=='(' || str[i]=='*' || str[i]=='/') //如果是左括号及乘除号,直接进栈
{
flag = true;
Push(cha,str[i]);
}
else if(str[i]==')') //如果是右括号,一直出栈,直到遇到左括号。在这里左括号出栈就不应该放回去了。
{
flag = true;
char * temp = new char;
Pop(cha,temp); //这里既然是右括号,说明之前必定入栈了左括号,所以栈一定不为空,因此不必检测栈是否为空
while(*temp != '(')
{
out += " ";
out += *temp; // 符号前后都加上空格,以便后面根据空格分隔字符串,区分操作数和操作符
out += " ";
Pop(cha,temp);
}
}
else if(str[i]=='+' || str[i]=='-') //如果是加减号,则将所有的符号出栈,除非遇到了左括号
{
flag = true;
char * temp = new char;
if(!Emptystack(cha)) //如果栈不为空则出栈
{
while(!Emptystack(cha))
{
Pop(cha,temp);
if(*temp=='(') //如果最后遇到了左括号,还得再入栈回去,要不然右括号来了又怎么找到左括号呢?同时还应该跳出循环
{
Push(cha,*temp);
break; //跳出while循环
}
out += " ";
out += *temp;
out += " ";
}
}
// 出栈后还得将当前符号入栈
Push(cha,str[i]);
}
}
// 最后表达式已经全部处理了,此时应该将栈中的所有符号全部出栈
{
char * temp = new char; //加上大括号,让*temp变成局部变量
while(!Emptystack(cha))
{
Pop(cha, temp);
out += " ";
out += *temp;
out += " ";
}
}
// 由于之前符号的前后都加了一个空格,如果两个符号接连输出则符号间就会有两个空格,可能会导致后面不方便处理。
// 在这里我们对out字符串进行一下整理工作,去掉多余的空格
{
//cout <<out<<endl;
bool space = false; // 当当前处理的字符前一个字符是空格的时候为true
for(int i=0;i<out.length();i++)
{
if(space == false) //如果前一个字符不是空格,就直接将当前字符复制到output中
output += out[i];
else if(space == true && out[i] != ' ') //如果前一个字符是空格,但当前字符不是空格,就将当前字符复制到output中
output += out[i];
//出现两个空格的情况,后一个空格就不复制到output中去就行了。
//最后检测当前字符是否空格,以用于对下一个字符进行处理。
//这一句话一定不能放在上面判断语句之前
//这个很好理解,就假设当前字符串为空格,如果放到上面判断语句之前,想想会发生什么状况
if(out[i] == ' ')
space = true;
else
space = false;
}
}
return output;
}
// 根据后缀表达式字符串,计算运算结果
double Calculate(string str)
{
double * result = new double;
doublestack * dou = new doublestack;
const char *split = " "; //按照空格对字符串进行分割
char * p = strtok(const_cast<char *>(str.c_str()),split);
while (p)
{
if(*p == '+' || *p == '-' || *p == '*' || *p == '/')
{
if (Emptystack(dou))
{
//如果已经到符号位了,而数字栈却为空,说明后缀表达式有误。
//分两种情况考虑,一种是错误的或者不规范的输入,另一种是我的程序本身对某些情况考虑不足
printf("The Reverse Polish Notation is wrong! Please check the expression which you input.\n");
return 0.0;
}
double *a = new double; //用于存储从栈中弹出的两个操作数
double *b = new double;
Pop(dou,a);
Pop(dou,b);
double c; //用于存储两个操作数操作结果
switch(*p)//在这里减号和除号要注意操作数的顺序!是后出栈的减去或除以先出栈的,也即*b-/*a
{
case '+':
c = (*b) + (*a);
break;
case '-':
c = (*b) - (*a);
break;
case '*':
c = (*b) * (*a);
break;
case '/':
c = (*b) / (*a);
break;
}
Push(dou,c); //将操作结果入栈
}
else // 否则则认为是操作数,这些操作数必须先转换成double类型再入栈!
{
double num;
istringstream temp(p);
temp>>num;
Push(dou,num);
}
p=strtok(NULL,split);
}
Pop(dou,result); //将最后的运行结果出栈
return *result;
}
int main()
{
string original; //运算表达式,也即中缀表达式
string nipolish; //后缀表达式,也即逆波兰表达式
double result;
cout<<"Please input a expression :"<<endl;
//cin>>original; //cin认为一个空格是输入的结束,getline是可以输入空格的,以回车作为结束符
getline(cin,original); //用getline 而不用cin>>输入的好处是可以接受空格,例如9.3 / 3这样的输入是可以接受的,可以处理的。
nipolish = Reverse(original);
cout<<"The Reverse Polish Notation is:"<<endl<<nipolish<<endl;
result = Calculate(nipolish);
cout<<"The result of expression is "<<result<<endl;
return 0;
}
最后附上几组测试用例:
9+(3-1)*3+10/2 = 20
9.7+3.3+100/25+3.7*4+(7-2.3)*5 = 55.3
(4-6)*3 = -6
自己定义栈还是挺麻烦的,下面使用STL实现的逆波兰算法。
#include <iostream>
#include <stack>
#include <string>
#include <sstream>
using namespace std;
// 将标准四则运算表达式(也即中缀运算表达式)转换成后缀运算表达式
string Reverse(string str)
{
string out = "";
string output = "";
stack<char>charstack;
int i = 0;
bool flag = false; // flag = false 表示数字是连续的,比如321是一个操作数;否则则认为中间有操作符
for(i=0; i<str.length(); i++)
{
if(str[i]>= '0' && str[i] <= '9')
{
if(flag == false)
out += str[i];
else
{
out += " ";
out += str[i];
flag = false; // flag 重新置成false
}
}
else if(str[i]=='.')
out += str[i]; //如果是小数点,直接输出即可
else if(str[i]=='(' || str[i]=='*' || str[i]=='/') //如果是左括号及乘除号,直接进栈
{
flag = true;
charstack.push(str[i]);
}
else if(str[i]==')') //如果是右括号,一直出栈,直到遇到左括号。在这里左括号出栈就不应该放回去了。
{
flag = true;
char temp = charstack.top();
charstack.pop();//这里既然是右括号,说明之前必定入栈了左括号,所以栈一定不为空,因此不必检测栈是否为空
while(temp != '(')
{
out += " ";
out += temp; // 符号前后都加上空格,以便后面根据空格分隔字符串,区分操作数和操作符
out += " ";
temp = charstack.top();
charstack.pop();
}
}
else if(str[i]=='+' || str[i]=='-') //如果是加减号,则将所有的符号出栈,除非遇到了左括号
{
flag = true;
char temp;
if(!charstack.empty()) //如果栈不为空则出栈
{
while(!charstack.empty())
{
temp = charstack.top();
charstack.pop();
if(temp=='(') //如果最后遇到了左括号,还得再入栈回去,要不然右括号来了又怎么找到左括号呢?同时还应该跳出循环
{
charstack.push(temp);
break; //跳出while循环
}
out += " ";
out += temp;
out += " ";
}
}
// 出栈后还得将当前符号入栈
charstack.push(str[i]);
}
}
// 最后表达式已经全部处理了,此时应该将栈中的所有符号全部出栈
{
char temp; //加上大括号,让*temp变成局部变量
while(!charstack.empty())
{
temp = charstack.top();
charstack.pop();
out += " ";
out += temp;
out += " ";
}
}
// 由于之前符号的前后都加了一个空格,如果两个符号接连输出则符号间就会有两个空格,可能会导致后面不方便处理。
// 在这里我们对out字符串进行一下整理工作,去掉多余的空格
{
//cout <<out<<endl;
bool space = false; // 当当前处理的字符前一个字符是空格的时候为true
for(int i=0;i<out.length();i++)
{
if(space == false) //如果前一个字符不是空格,就直接将当前字符复制到output中
output += out[i];
else if(space == true && out[i] != ' ') //如果前一个字符是空格,但当前字符不是空格,就将当前字符复制到output中
output += out[i];
//出现两个空格的情况,后一个空格就不复制到output中去就行了。
//最后检测当前字符是否空格,以用于对下一个字符进行处理。
//这一句话一定不能放在上面判断语句之前
//这个很好理解,就假设当前字符串为空格,如果放到上面判断语句之前,想想会发生什么状况
if(out[i] == ' ')
space = true;
else
space = false;
}
}
return output;
}
// 根据后缀表达式字符串,计算运算结果
double Calculate(string str)
{
double result;
stack<double>doublestack;
const char *split = " "; //按照空格对字符串进行分割
char * p = strtok(const_cast<char *>(str.c_str()),split);
while (p)
{
if(*p == '+' || *p == '-' || *p == '*' || *p == '/')
{
if (doublestack.empty())
{
//如果已经到符号位了,而数字栈却为空,说明后缀表达式有误。
//分两种情况考虑,一种是错误的或者不规范的输入,另一种是我的程序本身对某些情况考虑不足
printf("The Reverse Polish Notation is wrong! Please check the expression which you input.\n");
return 0.0;
}
double a; //用于存储从栈中弹出的两个操作数
double b;
a = doublestack.top();
doublestack.pop();
b = doublestack.top();
doublestack.pop();
double c; //用于存储两个操作数操作结果
switch(*p)//在这里减号和除号要注意操作数的顺序!是后出栈的减去或除以先出栈的,也即*b-/*a
{
case '+':
c = (b) + (a);
break;
case '-':
c = (b) - (a);
break;
case '*':
c = (b) * (a);
break;
case '/':
c = (b) / (a);
break;
}
doublestack.push(c); //将操作结果入栈
}
else // 否则则认为是操作数,这些操作数必须先转换成double类型再入栈!
{
double num;
istringstream temp(p);
temp>>num;
doublestack.push(num);
}
p=strtok(NULL,split);
}
result = doublestack.top();//将最后的运行结果出栈
doublestack.pop();
return result;
}
int main()
{
string original; //运算表达式,也即中缀表达式
string nipolish; //后缀表达式,也即逆波兰表达式
double result;
cout<<"Please input a expression :"<<endl;
//cin>>original; //cin认为一个空格是输入的结束,getline是可以输入空格的,以回车作为结束符
getline(cin,original); //用getline 而不用cin>>输入的好处是可以接受空格,例如9.3 / 3这样的输入是可以接受的,可以处理的。
nipolish = Reverse(original);
cout<<"The Reverse Polish Notation is:"<<endl<<nipolish<<endl;
result = Calculate(nipolish);
cout<<"The result of expression is "<<result<<endl;
return 0;
}