逆波兰表达式被广泛应用于编译原理中,但是近来在研究计算一元一次方程的时候发现通过逆波兰算法计算一元一次方程会更简单,原因是逆波兰表达式有一个其他的算法不能比拟的优点–拆括号(关于一元一次方程的算法程序,我会在以后陆续登载)。
标准的表达式如"A+B",在数学上学名叫中缀表达式(Infix Notation),原因是运算符号在两个运算对象的中间。相对应的还有前缀表达式(Prefix Notation),如:"+ - A * B C D",转换成中缀表达式为:"A - B * C + D";后缀表达式(Postfix Notation),比如前所述的中缀表达式转换为后缀表达式为:"A B C * - D +"。为了纪念波兰数学家鲁卡谢维奇(Jan Lukasiewicz),前缀表达式被称作波兰表达式,后缀表达式称为逆波兰表达式(Reverse Polish Notation)。
后缀表达式的优点是显而易见的,编译器在处理时候按照从左至右的顺序读取逆波兰表达式,遇到运算对象直接压入堆栈,遇到运算符就从堆栈提取后进的两个对象进行计算,这个过程正好符合了计算机计算的原理。
后缀表达式比前缀表达式更加易于转换,并且它的最左面一定为数字,这一点在实际编程的时候就会体会到它的好处了。
逆波兰表达式有一个更大的优点,就是拆括号,根据运算符的级别将中缀表达式转换成逆波兰表达式后,运算顺序就已经替代了运算符的级别,这样也避免了括号提高运算级别的特殊处理。
现在我就先简单介绍一下从中缀表达式转换为逆波兰表达式的标准算法。
a)给出一个中缀表达式1*(2+3)
b)系统先定义两个先进后出的堆栈:运算符号栈(简称入栈in),后缀表达式输出符号栈(简称出栈out)
c)系统按从左至右的顺序读取中缀表达式
d)读入数字直接压入出栈(out)
e)读入第一个运算符直接压入入栈(in)
f)读入"("直接压入入栈(in),此时两栈的数据为:in 1 ; out *,(
e)将第二次读取的运算符"+"与入栈中的栈顶运算符"("进行比较,高于栈顶级别的直接进 栈,低于或等于栈顶级别的要将入栈in解栈(即出栈),按次压入出栈中。比如现在入栈的运算顺序为(,*,/,此时若系统读取的运算符为+,级别比/要 低,此时要按/,*的顺序压入出栈out中,并在入栈中释放/和*符号,最后得到 in ( ; out /,*的结果。
f)最后读取")"时要找到入栈in中最近的"(",将其前面所有符号全部按后进先出的顺序压入出栈,并解压,"("与")"抵消。此时两栈的数据为:in 1,2,3,+ ; out *
g)系统读取中缀表达式结束后将入栈in中的所有符号按后进先出的顺序全部解压,并依次压入出栈out中,最后出栈的结果就应该为1,2,3,+,*
h)按先进先出的顺序将出栈out解压得到后缀标准表达式1,2,3,+,*
两个堆栈先后数据情况:
In | out |
| 1 |
* | 1 |
*,( | 1 |
*,( | 1,2 |
*,(,+ | 1,2 |
*,(,+ | 1,2,3 |
* | 1,2,3,+ |
| 1,2,3,+,* |
将中缀表达式转换成逆波兰表达式过程中,要注意的是"("无论入栈中级别为何直接入栈,在遇到")"时候找到最后进入的"(",并把"("前面所有的符号都压入出栈。我在编程中就犯过错误,仅凭运算符的级别来判断,结果出现错误的结果。
在上面简单概述了逆波兰表达式的用途,中缀表达式转换逆波兰表达式的标准算法,还有逆波兰表达式的计算方法。下面,我将给出中缀表达式转换逆波兰表达式时各运算符的运算级别。
运算符的运算级别即方程式运算的先后级别,如下:
运算符 | 级别 |
空格 | 0 |
) | 1 |
( | 2 |
+,- | 3 |
*,/ | 4 |
运算对象 | -1 |
我现在给出中缀表达式转换逆波兰表达式的C#源代码,该代码在.net2005运行通过。由于时间原因部分代码未作优化,希望有兴趣的朋友能请给我提出更好的建议
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
namespace TEST{
/// <summary>
/// 操作符结构
/// </summary>
public struct Operator
{
public string OperatorStack;
public int Level;
public Operator(string OperatorStack, int Level)
{
this.OperatorStack = OperatorStack;
this.Level = Level;
}
/// <summary>
/// 操作符运算级别
/// </summary>
/// <param name="strOperator">操作符</param>
/// <returns>操作运算符,空格返回0,出错返回-1</returns>
private int OperatorLevel(string strOperator)
{
switch (strOperator)
{
case "+":
return 3;
case "-":
goto case "+";
case "*":
return 4;
case "/":
goto case "*";
case "(":
return 2;
case ")":
return 1;
case " ":
return 0;
case "[":
return -2;
default:
return -1;
}
}
/// <summary>
/// 将中缀表达式转换为逆波兰表达式
/// </summary>
/// <param name="Expr
/// <returns>标准逆波兰表达式</returns>
public string RpnExpr
{
try
{
//加入结束标记?
string strExpr
//定义出栈和入栈堆栈
string[] strNum = Expr
int intNum = strNum.GetLength(0);
//操作运算符堆栈
Stack oper = new Stack();
//定义输出堆栈
Stack output = new Stack();
//定义前缀表达式字符读取指针
int i = 0;
//定义当前读取数字数组指针
int n = 0;
//定义操作运算符级别函数
Operator op = new Operator();
//输出堆栈的大小
int intStackCount = 0;
//从左到右读取前缀表达式
while (i < strExpr
{
//读取一个字符
string strChar = strExpr
if (strChar != "#")
{
//取字符的运算级别
int intLevel = this.OperatorLevel(strChar);
if (intLevel == 0)
//遇空格读取下一字符
{
i++;
}
else if (intLevel == -1)
//数字直接推入输出堆栈
{
for (int m = n; m < strNum.GetLength(0); m++)
{
if (strNum[m] != "")
{
//移动数组指针
n = m + 1;
//将数字直接推入堆栈
output.Push(strNum[m]);
//移动字符串读取指针
i = i + strNum[m].Length;
break;
}
}
}
else //操作字符根据运算字符级别推入运算符堆栈
{
if (oper.Count == 0)
{
//运算符堆栈为空,直接推入堆栈
oper.Push(new Operator(strChar, intLevel));
//移动字符读取指针
i++;
}
else
{
op = (Operator)oper.Peek();
if (intLevel > op.Level || intLevel == 2)
{
//运算字符比运算符堆栈最后的级别高或者运算符为'('直接推入运算符堆栈
oper.Push(new Operator(strChar, intLevel));
//移动字符读取指针
i++;
}
else
{
//运算字符不高于运算符堆栈最后的级别,则将运算符堆栈出栈,直到比其高为止
intStackCount = oper.Count;
for (int m = 0; m < intStackCount; m++)
{
op = (Operator)oper.Peek();
if (op.Level >= intLevel)
{
//将操作符出栈并压入输入堆栈
output.Push(op.OperatorStack);
int l = op.Level;
oper.Pop();
if (l == 2)
{
//如果操作符堆栈中最后的操作符为'('则停止出栈
i++;
break;
}
}
else
{
//直到运算符已经高出运算符栈中最后的级别,则入栈
oper.Push(new Operator(strChar, intLevel));
i++;
break;
}
}
}
}
}
}
else
{
//读取前缀表达式结尾将运算符堆栈直接压入输出堆栈
intStackCount = oper.Count;
for (int m = 0; m < intStackCount; m++)
{
op = (Operator)oper.Peek();
output.Push(op.OperatorStack);
oper.Pop();
}
i++;
}
}
//形成逆波兰表达式输出字符串
object[] strOutput = output.ToArray();
string str = Convert.ToString(strOutput[strOutput.GetLength(0) - 1]);
for (int m = strOutput.GetLength(0) - 2; m >= 0; m--)
{
if (Convert.ToString(strOutput[m]) != "(" && Convert.ToString(strOutput[m]) != ")")
{
str = str + "," + Convert.ToString(strOutput[m]);
}
}
return str;
}
catch
{
throw;
}
}
/// <summary>
/// 解逆波兰表达式
/// </summary>
/// <param name="Expr
/// <returns>逆波兰表达式的解</returns>
public double ComplieRpnExp(string Expr
{
try
{
//拆分逆波兰表达式
string[] strNum = Expr
int intLenth = strNum.GetLength(0);
//定义数字堆栈
Stack number = new Stack();
for (int i = 0; i < intLenth; i++)
{
int intLevel = this.OperatorLevel(strNum[i]);
if (intLevel == -1)
{
//如果为数字则直接压入数字堆栈
number.Push(Convert.ToDouble(strNum[i]));
}
else if (intLevel > 2)
{
//为符号则将数字堆栈后两个数据解压并计算,将计算结果压入堆栈
if (number.Count > 1)
{
//必须保证数字堆栈有2个以上数字
double dLastin = Convert.ToDouble(number.Pop());
double dFirstin = Convert.ToDouble(number.Pop());
double dResult = this.ComplieRpnExp(dLastin, dFirstin, strNum[i]);
if (!Convert.IsDBNull(dResult))
{
//压入计算结果
number.Push(dResult);
}
}
}
}
double d = Convert.ToDouble(number.Pop());
number.Clear();
return d;
}
catch
{
throw;
}
}
/// <summary>
/// 计算逆波兰表达式
/// </summary>
/// <param name="dLastin">最后压入数字堆栈的数字</param>
/// <param name="dFirstin">首先压入数字堆栈的数字</param>
/// <param name="oper">操作运算符</param>
/// <returns>返回计算结果</returns>
private double ComplieRpnExp(double dLastin, double dFirstin, string oper)
{
switch (oper)
{
case "+":
return dFirstin + dLastin;
case "-":
return dFirstin - dLastin;
case "*":
return dFirstin * dLastin;
case "/":
return dFirstin / dLastin;
default:
return 0;
}
}
}
}
到此逆波兰表达式已经全部搞定,现在我可以通过将中缀表达式转换成逆波兰表达式再计算,不会担心括号的问题。
一元多次方程乃至一元多次方程的解都迎刃而解。