K:双栈法求算术表达式的值

时间:2022-05-11 16:47:41

相关介绍:

 该算法用于求得一个字符串形式的表达式的结果。例如,计算1+1+(3-1)*3-(21-20)/2所得的表达式的值,该算法利用了两个栈来计算表达式的值,为此,称为双栈法,其实现简单且易于理解。但其要求将我们平时所看到的表达式的模式转化为完全加括号的形式。如表达式,1+1+(3-1)*3-(21-20)/2是我们平时习惯上的样子,其完全加括号的形式为,(((1+1)+((3+1)*2))-((21-20)/2))。由此可知,计算一个字符串形式的表达式有两个任务,第一是将输入的算术表达式转化为一个完全加括号的算术表达式,第二是将一个完全加括号的算术表达式进行运算,求得运算的结果。为方便起见,这里只考虑加减乘除(+、-、×、÷)这四个基本运算

将算术表达式转化为完全加括号:

其基本思想如下:

  1. 初始化一个运算符栈和一个操作数栈
  2. 从算术表达式输入的字符串中从左到右的读取一个字符
  3. 若当前字符为操作数,则直接将该操作数压入操作数栈中
  4. 若当前字符是左括号"("时,将其压入运算符栈中
  5. 若当前字符为运算符时,则:
    1. 当运算符栈为空的时候,则将其压入运算符栈中
    2. 当此运算符的优先级高于栈顶元素的运算符的时候,则将此运算符压入操作数栈中,否则,弹出运算符栈顶元素和操作数栈顶的两个元素,为其添加上相应的运算符以及左括号和右括号之后,将其压入操作数栈中,将其看成一个整体
  6. 若当前字符为右括号")"时,反复将栈顶元素弹出,每次弹出一个运算符的时候,从操作数栈中弹出栈顶的两个元素为其添加上相应的运算符以及左右括号之后,再将其压入操作数栈中,将其看成一个整体。直至运算符栈的栈顶元素为左括号为止,再将左括号出栈并丢弃。
  7. 若读取还未完毕,重复步骤2
  8. 若读取完毕,则将栈中剩余的所有运算符依次弹出,每次弹出一个运算符时,同时弹出操作数栈的两个元素,并为其添加上相应的运算符以及左右括号之后,将其作为一个整体,压入操作数栈中。直到运算符栈为空为止,则操作数栈中的结果,即为所得的结果。

运算符的优先级如下表所示:

运算符 +(加)、-(减) *(乘)、/(除)
优先级 1 2

其中,其数值越大的表示其运算符的优先级越高。

示例代码如下:

package queueandstack;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
/**
* 该类用于演示使用双栈法求解算术表达式的结果
* @author 学徒
*
*/
public class DoubleStackGetResult
{ public static void main(String[] args)
{
DoubleStackGetResult ds=new DoubleStackGetResult();
System.out.println(ds.ComplementBrackets("1+1+(3+1)*2-(21-20)/2"));
}
//该方法用于实现一个符号表
private static Map<String,Integer> getMap()
{
Map<String,Integer> temp=new HashMap<String,Integer>();
//定义各个运算符的优先级,其中,x和÷字符用于兼容
temp.put("*", 2);
temp.put("/", 2);
temp.put("×",2);
temp.put("÷",2);
temp.put("+", 1);
temp.put("-",1);
return temp;
}
/**
* 该方法用于将输入的,习惯上的算术表达式转化为完全加括号的形式
* @param input 输入的习惯上的算术表达式
*/
//该符号表用于定义运算符的优先级
private Map<String,Integer> table=getMap();
//该字符串为用于匹配数字的
private String rexNumber="\\d+";
private String rexOperator="[((+\\-*/×÷))]";
//操作数栈
Stack<String> number=new Stack<String>();
//运算符栈
Stack<String> save=new Stack<String>();
/**
* 该方法用于将中序表达式转化为后序表达式,并对其转化后的表达式以字符串的形式进行返回
* @return 后序表达式
*/
public String ComplementBrackets(String input)
{
//用于获得字符串中的数字所组成的数组
String[] numbers=input.split(rexOperator);
//用于指示是获取了第几个数字数组中的数字整体
int order=0;
//用于指示当前字符的指针
int i=0;
while(i<input.length())
{
//获得当前字符
String thisString=String.valueOf(input.charAt(i));
//当前该字符为运算符或者括号时,即当前该字符不为数字时
if(thisString.matches(rexOperator))
{
//当当前字符不为左括号或者右括号时(即为运算符)
if(!thisString.matches("[()()]"))
{
//用于记录栈顶元素的优先级
int temporary=0;
//获取当前字符的优先级
int present=table.get(thisString);
//当操作数的栈不为空的时候
if(!save.isEmpty())
{
//查看栈顶元素的字符以及其优先级
String top=save.peek();
if(!top.matches("[((]"))
{
temporary=table.get(top);
}
}
//当栈顶元素的操作符的优先级比当前操作符的优先级还要高或者相同时,对其进行弹出操作,直到栈顶元素的优先级比当前操作符的优先级要低
if(temporary>=present)
{
while(!save.isEmpty()&&table.get(save.peek())>=present)
{
String number1=number.pop();
String number2=number.pop();
number.push("("+number2+save.pop()+number1+")");
}
}
save.push(thisString);
}
//当当前的字符为左括号的时候,直接将其压入栈中
else if(thisString.matches("[((]"))
{
save.push(thisString);
}
//当当前的字符为右括号的时候,将其栈中的元素一直弹出,直至遇到左括号结束,并将左括号弹出
else
{
while(!save.peek().matches("[((]"))
{
String number1=number.pop();
String number2=number.pop();
number.push("("+number2+save.pop()+number1+")");
}
//弹出其左括号
save.pop();
/*String number1=number.pop();
String number2=number.pop();
number.push("("+number2+save.pop()+number1+")");*/
}
i++;
}
//当前该字符为数字的时候
if(thisString.matches(rexNumber))
{
//用于存储数字数组中的数字字符串
String numberString=null;
do
{
numberString=numbers[order];
//当数字字符串中的数字不为空时(由于可能会是空字符串的出现),将整个中序表达式的字符串的指针进行向右移动
if(!numberString.trim().equals(""))
{
i+=numberString.length();
order++;
break;
}
else
{
order++;
}
}while(true);
//将数字直接压入操作数栈中
number.push(numberString);
}
}
//将栈中剩余的字符进行弹出
while(!save.isEmpty())
{
String number1=number.pop();
String number2=number.pop();
number.push("("+number2+save.pop()+number1+")");
}
return number.pop();
}
} 运行结果如下:
(((1+1)+((3+1)*2))-((21-20)/2))

计算完全加括号表达式的结果:

其基本思想如下:

  1. 初始化两个栈,一个用于保存运算符,一个用于保存操作数
  2. 从左往右依次遍历字符串表达式的每个字符
  3. 将操作数压入操作数栈中
  4. 将运算符压入运算符栈中
  5. 忽略左括号
  6. 在遇到右括号时,弹出一个运算符以及所需数量的操作数,并将运算符和操作数的运行结果压入到操作数栈中
  7. 处理完最后一个右括号,操作数栈上只会有一个值,它就是表达式的值

这种方法不难理解,每当算法遇到一个被括号包围并由一个运算符和两个操作数组成的子表达式时,它都能够将运算符和操作数的计算结果压入操作数栈中,这样的结果就是在输入中用这个运算所得的值代替了该子表达式,因此,用这个值代替子表达式得到的结果和原表达式相同,通过反复运用以上的规律,最终可以得到该表达式的解。

其示例代码如下:

/**
*
* 用于计算一个完全加括号的算术表达式的结果
* @param inputStr 其参数为完全加括号的算术表达式
*
*/
public double getResult(String inputStr)
{
//用于将输入的字符串分割成数字的正则表达式
String regex="[((+\\-*/×÷))]";
//用于获取得到数字
String[] numbers =inputStr.split(regex);
int order=0;
//两个栈,一个为操作数栈,一个为运算符栈
Stack<String> ops=new Stack<String>();
Stack<Double> vals=new Stack<Double>();
char[] input=inputStr.toCharArray();
//用于遍历的字符的指针
int index=0;
while(index<input.length)
{
//读取对应的字符
char ch=input[index];
//忽略左括号
if(ch=='('||ch=='(');
else if(ch=='+')
ops.push(String.valueOf(ch));
else if(ch=='-'||ch=='-')
ops.push(String.valueOf(ch));
else if(ch=='*'||ch=='×')
ops.push(String.valueOf(ch));
else if(ch=='/'||ch=='÷')
ops.push(String.valueOf(ch));
else if(ch==')'||ch==')')
{
//当为右括号的时候,弹出运算符以及操作数,计算结果并压入栈中
String op=ops.pop();
double v=vals.pop();
if(op.equals("+"))
v=vals.pop()+v;
else if(op.equals("-")||op.equals("-"))
v=vals.pop()-v;
else if(op.equals("*")||op.equals("×"))
v=vals.pop()*v;
else if(op.equals("/")||op.equals("÷"))
v=vals.pop()/v;
vals.push(v);
}
else
{
//用于存储数字数组中的数字字符串
String numberString=null;
do
{
numberString=numbers[order];
//当数字字符串中的数字不为空时(由于可能会是空字符串的出现),将整个中序表达式的字符串的指针进行向右移动
if(!numberString.trim().equals(""))
{
index+=numberString.length()-1;
order++;
break;
}
else
{
order++;
}
}while(true);
vals.push(Double.parseDouble(numberString));
}
++index;
}
return vals.pop();
}

回到目录|·(工)·)