逆波兰表达式
本文为作者在学习离散数学的命题表达式部分所作,因此主要解决的问题是将一个中缀形式的命题表达式转化成为对应的后缀表达式(逆波兰表达式),但是本文采取的算法也可以适用于一般的中缀向后缀表达式转化。
概念
逆波兰表示法(Reverse Polish notation,RPN,或逆波兰记法),是一种是由波兰数学家扬·武卡谢维奇1920年引入的数学表达式方式,在逆波兰记法中,所有操作符置于操作数的后面,因此也被称为后缀表示法。逆波兰记法不需要括号来标识操作符的优先级。(摘自wiki)
举例来讲,表达“三加四”时正常的表示方法是 “3 + 4”(又被称为中缀表达式),而按照逆波兰表示法,其表达式应为“3 4 +”。如果有多个操作符,操作符置于第二个操作数的后面,所以常规中缀记法的“3 - 4 + 5”,在逆波兰记法中写作“3 4 - 5 + ”,表示先3减去4,再加上5。
逆波兰表达式的求值使用堆栈结构很容易实现,并且能很快求值——操作数入栈;遇到操作符时,操作数出栈,求值,将结果入栈;当一遍后,栈顶就是表达式的值。因此曾被广泛应用于计算器。
算法
1.建立两个数组,一个为P存储数据,一个为OP存储运算符
2.将字符串从左向右遍历,把数据压入数据栈,把运算符压入运算符数组OP
关于运算符压入的规则:
⑴ 如果OP为空或者为待压入操作符为左括号则直接将运算符压入
⑵ 如果待压入操作符的优先级大于数组最末操作符则直接压入
⑶ 如果数组最末操作符的优先级大于待压入操作符,则将OP最末的操作符依次压入N直到遇到左括号或者(OP为空)最末操作符优先级小于待压的则停止,此时压入该操作符到OP中
⑷ 如果遇到右括号则将OP中的元素依次弹出压入N中直到遇到左括号为止,同时弹出左括号并且右括号不压入OP
⑸ 最后将OP中的元素依次弹出并压入N中,将N弹出并逆序输出则是最终的逆波兰表达式
事实上,本算法完全可以使用c中的栈结构代替数组,而且也更为方便。但由于目前作者对于栈结构了解的太少,故暂用数组代替
代码实现
说明:为方便编程,作者将输入简化,本例中,所有的命题符号都是使用大写字母来表示,之外,本例中允许出现的运算符有‘!’(非), ‘&’(合取), ‘|’(析取), ‘^’(蕴含), ‘~’(双蕴含) 以及括号 ‘(’ 和 ‘)’,这几个运算符的优先级如其排列所示。在本算法中,需要对各运算符的优先级进行量化,结果如下:
int com_pri(char a) //返回各个运算符的优先级
{
int temp = 0;
switch (a)
{
case '(': temp = 0;
break;
case')':temp = 0;
break;
case'~':temp = 1;
break;
case'^':temp = 2;
break;
case'|':temp = 3;
break;
case '&':temp = 4;
break;
case'!':temp = 5;
break;
}
return temp;
}
在本例中,作者也做了一个初步的输入检查,用以提醒一些非法输入。(当然,此输入检查仍有较大漏洞)
while ((s[0] < 'A' || s[0]>'Z')&&(s[0]!='(')){
cout << "要求所有的命题符号大写,不可以有数字及小写字母等" << endl;
cout << "请输入合格的中缀表达式:" << endl;
cin >> s;
}
整个工程的源代码如下:
#include<iostream>
#include<string>
using namespace std;
int com_pri(char a);//返回各运算符的优先级
int main()
{
cout << "请输入一个中缀表达式:" << endl;
string s;
cin >> s; //保存输入的中缀表达式
while ((s[0] < 'A' || s[0]>'Z')&&(s[0]!='(')){
cout << "要求所有的命题符号大写,不可以有数字及小写字母等" << endl;
cout << "请输入合格的中缀表达式:" << endl;
cin >> s;
}
char p[50] = { '\0' };//存储数据,即命题
char op[50] = { '\0' };//存储运算符
char *p1, *p2, *op1, *op2;
p1 = p2 = p;//p1,p2两个指针用来存储p数组的首位和末位
op1 = op2 = op;//op1,op2两个指针用来存储op数组的首位和末位
for (int i = 0; i < s.length(); i++)
{
char t = s.at(i);
if (t >= 'A'&&t <= 'Z')//t是一个命题,入栈
{
*p2 = t;
p2++;
continue;
}
//若t是运算符,有以下压入规则
// 如果OP为空或者为待压入操作符为左括号则直接将运算符压入
if ((op2 == op1) || (t == '('))
{
*op2 = t;
op2++;
continue;
}
if (t == ')')
{
//如果遇到右括号则将OP中的元素依次弹出压入N中直到遇到左括号为止,同时需要舍去左括号并且右括号不再压入OP中
while (*(op2 - 1) != '(')
{
*p2 = *(op2 - 1);
*(op2 - 1) = '\0';
p2++;
op2--;
}
op2--;
*op2 = '\0';
continue;
}
//如果待压入操作符的优先级大于数组最末操作符则直接压入
if (com_pri(t) > com_pri(*(op2 - 1)))
{
*op2 = t;
op2++;
continue;
}
else
{
//如果数组最末操作符的优先级大于待压入操作符,则将OP最末的操作符依次压入N直到遇到左括号或者(OP为空)最末操作符优先级小于待压的则停止,此时压入该操作符到OP中
if ((t == '!') && (*(op2 - 1) == '!'))
{
*op2 = t;
op2++;
continue;
}
else
{
while ((com_pri(*(op2 - 1)) >= com_pri(t)) && (*(op2 - 1) != '('))
{
*p2 = *(op2 - 1);
op2--;
p2++;
}
*op2 = t;
op2++;
continue;
}
}
}
for (int j = strlen(op)-1; j >= 0; j--)//将OP中的元素依次弹出并压入N中
{
*p2 = op[j];
p2++;
}
cout << p << endl;
//system("pause");
return 0;
}
int com_pri(char a)//返回各个运算符的优先级
{
int temp = 0;
switch (a)
{
case '(': temp = 0;
break;
case')':temp = 0;
break;
case'~':temp = 1;
break;
case'^':temp = 2;
break;
case'|':temp = 3;
break;
case '&':temp = 4;
break;
case'!':temp = 5;
break;
}
return temp;
}