一、程序要求

解析一般数学算式,实现简单的带括号的加减乘除运算。

二、基本思路

前面两篇介绍了直接解析字符串和用数组容器辅助解析的两种方式,这次再介绍最常用的解析算法——解析后缀表达式(逆波兰表达式)。

三、逆波兰表达式及其得到算法

1、逆波兰表达式

也即后缀表达式,指的是不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则)。(摘自百度),既然没了运算符的优先规则,那么计算机解析起来自然容易的多。

对于我们常见的表达式,称为中缀表达式,每个中缀表达式都有对应的后缀表达式。如:

中缀表达式:-2*(1+6/3)+4

后缀表达式:-2 1 6 3 / + * 4 +(这里为了区分负号和减号,我在数字与数字、数字与符号之间都加了空格,至于怎么从中缀表达式得到后缀表达式,后面有介绍及参考程序)

而在解析后缀表达式时,只需要遵守以下原则即可:

从左往右遍历

遇到数字直接放入容器

遇到运算符,将最后两个数字取出,进行该运算,将结果再放入容器

遍历结束后,容器中的数字即为运算结果

按这个过程走下来,自然而然的想到用栈是最合适的。

现只需想办法由输入的中缀表达式转为后缀表达式即可完成解析。

2、由中缀表达式得到后缀表达式的算法

由中缀表达式得到后缀表达式,只要遵守以下步骤即可:

首先设置运算符的优先级(这样设置也是为了简化程序):

”null” 栈顶若为空,假设优先级为0

“(” 优先级设为1

“+-” 优先级设为2

“*/” 优先级设为3

从左向右遍历中缀表达式

遇到数字直接输出

遇到符号

遇到左括号,直接压栈

遇到右括号,弹栈输出直到弹出左括号(左括号不输出)

遇到运算符,比较栈顶符号,若该运算符优先级大于栈顶,直接压栈;若小于栈顶,弹栈输出直到大于栈顶,然后将改运算符压栈。

最后将符合栈弹栈并输出

现根据这个原则,手动模拟一遍转换过程:

还是以-2*(1+6/3)+4为例

四、代码一

环境:

  • Eclipse Java EE IDE(Version: Oxygen.1a Release (4.7.1a))
  • jdk1.8.0_131

先写一个最基本的两位数四则运算方法,比较简单,没有写注释:

private static double doubleCal(double a1, double a2, char operator) throws Exception {
switch (operator) {
case '+':
return a1 + a2;
case '-':
return a1 - a2;
case '*':
return a1 * a2;
case '/':
return a1 / a2;
default:
break;
}
throw new Exception("illegal operator!");
}

  写一个获得优先级的方法:

private static int getPriority(String s) throws Exception {
if(s==null) return 0;
switch(s) {
case "(":return 1;
case "+":;
case "-":return 2;
case "*":;
case "/":return 3;
default:break;
}
throw new Exception("illegal operator!");
}

将中缀表达式转变为后缀表达式:

private static String toSufExpr(String expr) throws Exception {
System.out.println("将"+expr+"解析为后缀表达式...");
/*返回结果字符串*/
StringBuffer sufExpr = new StringBuffer();
/*盛放运算符的栈*/
Stack<String> operator = new Stack<String>();
operator.push(null);//在栈顶压人一个null,配合它的优先级,目的是减少下面程序的判断
/* 将expr打散分散成运算数和运算符 */
Pattern p = Pattern.compile("(?<!\\d)-?\\d+(\\.\\d+)?|[+\\-*/()]");//这个正则为匹配表达式中的数字或运算符
Matcher m = p.matcher(expr);
while (m.find()) {
String temp = m.group();
if (temp.matches("[+\\-*/()]")) { //是运算符
if (temp.equals("(")) { //遇到左括号,直接压栈
operator.push(temp);
System.out.println("'('压栈");
} else if (temp.equals(")")) { //遇到右括号,弹栈输出直到弹出左括号(左括号不输出)
String topItem = null;
while (!(topItem = operator.pop()).equals("(")) {
System.out.println(topItem+"弹栈");
sufExpr.append(topItem+" ");
System.out.println("输出:"+sufExpr);
}
} else {//遇到运算符,比较栈顶符号,若该运算符优先级大于栈顶,直接压栈;若小于栈顶,弹栈输出直到大于栈顶,然后将改运算符压栈。
while(getPriority(temp) <= getPriority(operator.peek())) {
sufExpr.append(operator.pop()+" ");
System.out.println("输出sufExpr:"+sufExpr);
}
operator.push(temp);
System.out.println("\""+temp+"\""+"压栈");
}
}else {//遇到数字直接输出
sufExpr.append(temp+" ");
System.out.println("输出sufExpr:"+sufExpr);
} } String topItem = null;//最后将符合栈弹栈并输出
while(null != (topItem = operator.pop())) {
sufExpr.append(topItem+" ");
}
return sufExpr.toString();
}

解析中缀表达式的方法:

public static String getResult(String expr) throws Exception {
String sufExpr = toSufExpr(expr);// 转为后缀表达式
System.out.println("开始计算后缀表达式...");
/* 盛放数字栈 */
Stack<Double> number = new Stack<Double>();
/* 这个正则匹配每个数字和符号 */
Pattern p = Pattern.compile("-?\\d+(\\.\\d+)?|[+\\-*/]");
Matcher m = p.matcher(sufExpr);
while (m.find()) {
String temp = m.group();
if (temp.matches("[+\\-*/]")) {// 遇到运算符,将最后两个数字取出,进行该运算,将结果再放入容器
System.out.println("符号"+temp);
double a1 = number.pop();
double a2 = number.pop();
double res = doubleCal(a2, a1, temp.charAt(0));
number.push(res);
System.out.println(a2 + "和" + a1 + "弹栈,并计算" + a2 + temp + a1);
System.out.println("数字栈:" + number);
} else {// 遇到数字直接放入容器
number.push(Double.valueOf(temp));
System.out.println("数字栈:" + number);
}
}
return number.pop() + "";
}

主方法,以-3.5*(4.5-(4+(-1-1/2)))测试

public static void main(String[] args) throws Exception {
String str = "-3.5*(4.5-(4+(-1-1/2)))";
System.out.println(getResult(str));
}

五、执行结果

六、简化过程分析
根据这个算法,在不需要解出后缀表达式的情况下,还可以将代码进一步简化。
在解析的过程的中,我们只需要按照以下原则:

使用两个栈,一个数字栈,一个符号栈
从左往右遍历表达式字符串
遇到数字,直接压入数字栈
遇到符号
遇到左括号,直接入符号栈
遇到右括号,”符号栈弹栈取栈顶符号b,数字栈弹栈取栈顶数字a1,数字栈弹栈取栈顶数字a2,计算a2 b a1 ,将结果压入数字栈”,重复引号步骤至取栈顶为左括号,将左括号弹出
遇到运算符,1)若该运算符的优先级大于栈顶元素的优先级,直接入符号栈。2)若小于,”符号栈弹栈取栈顶符号b,数字栈弹栈取栈顶数字a1,数字栈弹栈取栈顶数字a2,计算a2 b a1 ,将结果压入数字栈”,重复引号步骤至该运算符的优先级大于符号栈顶元素的优先级,然后将该符号入符号栈
遍历结束后,”符号栈弹栈取栈顶符号b,数字栈弹栈取栈顶数字a1,数字栈弹栈取栈顶数字a2,计算a2 b a1 ,将结果压入数字栈”,重复引号步骤至符号栈无符号(或数字栈只有一个元素),则数字栈的元素为运算结果
七、代码二
环境:

Eclipse Java EE IDE(Version: Oxygen.1a Release (4.7.1a))
jdk1.8.0_131
先写一个最基本的两位数四则运算方法,比较简单,没有写注释:

private static double doubleCal(double a1, double a2, char operator) throws Exception {
switch (operator) {
case '+':
return a1 + a2;
case '-':
return a1 - a2;
case '*':
return a1 * a2;
case '/':
return a1 / a2;
default:
break;
}
throw new Exception("illegal operator!");
}

写一个获得优先级的方法:

private static int getPriority(String s) throws Exception {
if(s==null) return 0;
switch(s) {
case "(":return 1;
case "+":;
case "-":return 2;
case "*":;
case "/":return 3;
default:break;
}
throw new Exception("illegal operator!");
}

解析表达式:

public static String getResult(String expr) throws Exception {
System.out.println("计算"+expr);
/*数字栈*/
Stack<Double> number = new Stack<Double>();
/*符号栈*/
Stack<String> operator = new Stack<String>();
operator.push(null);// 在栈顶压人一个null,配合它的优先级,目的是减少下面程序的判断 /* 将expr打散为运算数和运算符 */
Pattern p = Pattern.compile("(?<!\\d)-?\\d+(\\.\\d+)?|[+\\-*/()]");// 这个正则为匹配表达式中的数字或运算符
Matcher m = p.matcher(expr);
while(m.find()) {
String temp = m.group();
if(temp.matches("[+\\-*/()]")) {//遇到符号
if(temp.equals("(")) {//遇到左括号,直接入符号栈
operator.push(temp);
System.out.println("符号栈更新:"+operator);
}else if(temp.equals(")")){//遇到右括号,"符号栈弹栈取栈顶符号b,数字栈弹栈取栈顶数字a1,数字栈弹栈取栈顶数字a2,计算a2 b a1 ,将结果压入数字栈",重复引号步骤至取栈顶为左括号,将左括号弹出
String b = null;
while(!(b = operator.pop()).equals("(")) {
System.out.println("符号栈更新:"+operator);
double a1 = number.pop();
double a2 = number.pop();
System.out.println("数字栈更新:"+number);
System.out.println("计算"+a2+b+a1);
number.push(doubleCal(a2, a1, b.charAt(0)));
System.out.println("数字栈更新:"+number);
}
System.out.println("符号栈更新:"+operator);
}else {//遇到运算符,满足该运算符的优先级大于栈顶元素的优先级压栈;否则计算后压栈
while(getPriority(temp) <= getPriority(operator.peek())) {
double a1 = number.pop();
double a2 = number.pop();
String b = operator.pop();
System.out.println("符号栈更新:"+operator);
System.out.println("数字栈更新:"+number);
System.out.println("计算"+a2+b+a1);
number.push(doubleCal(a2, a1, b.charAt(0)));
System.out.println("数字栈更新:"+number);
}
operator.push(temp);
System.out.println("符号栈更新:"+operator);
}
}else {//遇到数字,直接压入数字栈
number.push(Double.valueOf(temp));
System.out.println("数字栈更新:"+number);
}
} while(operator.peek()!=null) {//遍历结束后,符号栈数字栈依次弹栈计算,并将结果压入数字栈
double a1 = number.pop();
double a2 = number.pop();
String b = operator.pop();
System.out.println("符号栈更新:"+operator);
System.out.println("数字栈更新:"+number);
System.out.println("计算"+a2+b+a1);
number.push(doubleCal(a2, a1, b.charAt(0)));
System.out.println("数字栈更新:"+number);
}
return number.pop()+"";
}

主方法,以-3.5*(4.5-(4+(-1-1/2)))测试

public static void main(String[] args) throws Exception {
String str = "-3.5*(4.5-(4+(-1-1/2)))";
System.out.println(getResult(str));
}

JAVA四则运算算法的更多相关文章

  1. 史上最全的java随机数生成算法分享(转)

    这篇文章主要介绍了史上最全的java随机数生成算法,我分享一个最全的随机数的生成算法,最代码的找回密码的随机数就是用的这个方法 String password = RandomUtil.generat ...

  2. 常用Java排序算法

    常用Java排序算法 冒泡排序 .选择排序.快速排序 package com.javaee.corejava; public class DataSort { public DataSort() { ...

  3. 使用Java练习算法常用的基本操作

    一.使用Java练习算法常常需要使用控制台的数据输入和输出,下面记录一下基本的使用方法: 基本用法 import java.util.*; public class Main { public sta ...

  4. JAVA经典算法40题及解答

    JAVA经典算法40题 [程序1]   题目:古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第四个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少? 1.程序分 ...

  5. Java基础算法集50题

    最近因为要准备实习,还有一个蓝桥杯的编程比赛,所以准备加强一下算法这块,然后百度了一下java基础算法,看到的都是那50套题,那就花了差不多三个晚自习的时间吧,大体看了一遍,做了其中的27道题,有一些 ...

  6. Java经典算法四十例编程详解+程序实例

    JAVA经典算法40例 [程序1]   题目:古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第四个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?   1.程 ...

  7. Java排序算法之直接选择排序

    Java排序算法之直接选择排序 基本过程:假设一序列为R[0]~R[n-1],第一次用R[0]和R[1]~R[n-1]相比较,若小于R[0],则交换至R[0]位置上.第二次从R[1]~R[n-1]中选 ...

  8. JAVA经典算法40题

    1: JAVA经典算法40题 2: [程序1] 题目:古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第四个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少? 3 ...

  9. Java与算法之(13) - 二叉搜索树

    查找是指在一批记录中找出满足指定条件的某一记录的过程,例如在数组{ 8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15 }中查找数字15,实现代码很简单 ...

随机推荐

  1. 4.8 Routing -- Specifying The URL Type

    1. 默认的路由器使用浏览器的hash来加载应用程序的开始状态并且当你移动时同步保持.目前,这依赖于浏览器中存在的hashchange事件. 2. 假设下面的路由器,输入/#/posts/new将会把 ...

  2. uva1025 dp

    这题说的是给了n个车站 从1号 车站到 n号车站,有m1辆车从1 开往n 有m2 辆车从n 开往1 一个人从1 车站 到达n 车站在T 时刻 要求再 车站呆的时间尽量少 dp[i][j] 表示 在 第 ...

  3. 使用Sed和Awk实现批量文件的文本替换

    摘要: 使用 Sed 完成文本替换操作任务是非常合适的.结合 find 命令,即可实现指定批量文件的文本替换.同时给出了Awk的解决方案作为对比. 问题 现在, 我要将一个原有Java项目中的一些包及 ...

  4. Sublime Text 3 配置Python3.x

    Sublime Text 3 配置Python3.x 一.Package Control 安装: 1,通过快捷键 ctrl+` 或者 View > Show Console 打开控制台,然后粘贴 ...

  5. ubuntu/centos/mac/windows 使用阿里源加速docker镜像下载

    官方下载docker比较慢,阿里提供云容器hub, 1.打开阿里容器hub https://dev.aliyun.com/search.html 该页面右上方有一个管理中心,点击进去 2.选择镜像加速 ...

  6. linux内核分析第六周-分析Linux内核创建一个新进程的过程

    Linux内核对进程管理是操作系统的重要任务之一. 此次实验就是了解内核创建一个新进程的大致过程. 为了简单,使用fork再用户态创建一个进程.代码如下: 下面是准备工作​​​ cd LinuxKer ...

  7. Java加密代码 转换成Net版

    java版本自己封装base64 package com.qhong; import java.io.UnsupportedEncodingException; import org.apache.c ...

  8. POJ 1780 Code(欧拉回路+非递归dfs)

    http://poj.org/problem?id=1780 题意:有个保险箱子是n位数字编码,当正确输入最后一位编码后就会打开(即输入任意多的数字只有最后n位数字有效)……要选择一个好的数字序列,最 ...

  9. Redis复制(replication)

    介绍 Redis支持简单的主从(master-slave)复制功能,当主Redis服务器更新数据时能将数据同步到从Redis服务器 配置 在Redis中使用复制功能非常容易 在从Redis服务器的re ...

  10. MySQL —— 基本查询方法

    MySQL —— 简单查询与按条件查询 在MySQL中从数据表中查询数据的基本语句时select语句.  select语句基本语法格式:      select 查询内容       from 表名  ...