您的位置:首页 > 编程语言 > Java开发

表达式计算 Java算法

2017-11-30 23:51 162 查看
一个关于带括号的四则运算简单算法,看题目



题目分析:

简单地说,题目就是要求我们写一个带有括号的四则运算算法.

我们先来以人脑的思维做一下这种四则运算:

首先, 看到+和-可以先忽略, 看到*(乘法)和/(除法)也可以先放一边, 优先找表达式里面有没有()括号(因为四则运算的优先级是规定好的嘛).

根据上述的规则, 在做 1-2+3*(4-5) 这道题目时, 优先执行小括号里面的内容 (4-5)=-1 , 表达式变成 1-2+3*(-1) , 然后乘除法优先于加减法, 执行 3*(-1)=-3 , 表达式变成 1-2+(-3) , 最后就是加减的平级运算, 依次计算后可以得到最终结果 -4.

其实这种四则运算小学就会了, 有的人看了上面的步骤可能都不耐烦了, 但是别急, 跟着思路来.

思路分析:

通过人脑思维做四则运算时, 我们发现其实判断的只有三个东西.

第一个就是进行运算的数字, 我们可以称其为 操作数

第二个是运算的符号(加减乘除), 可以称其为 操作符

第三个是判断是否有小括号, 有的话就要优先执行里面的内容, 其实也可以归类为上面的 操作符

除此之外, 我们在运算的时候, 是从左到右读取整个表达式的

碰到加减号是优先忽略的, 优先计算的是乘除法和小括号里面的内容, 计算完之后, 再回过头计算加减法

需要的技术分析:

通过思路分析我们发现, 计算的顺序是从左到右, 碰到加减法时,可以想象是把加减号和相关的数字先存入一个盒子中, 碰到乘除法时, 如果乘除号后面不是括号, 那么直接计算, 如果是括号, 那么同样的存入一个盒子中, 如果碰到括号了, 那么直接计算括号里面的表达式, 最后回过头, 取出盒子里面的数字和符号平级运算.

根据存入盒子这种做法, 优先计算的是后面的数字, 我们可以想到利用 来帮助我们. 为什么要用栈呢, 请看下面的图示 :







代码中的技术需求分析:

用两个集合模拟栈空间, 每次取出的时候从尾部取出(从栈顶弹栈), 一个集合存储数字, 另一个存储 运算符和前括号.

三个全局静态变量:

一个操作符标记变量 op, 用于标记本次运算的类型是加减乘除中的哪一个, 因为测试环境是jdk1.6, 还不支持在switch中传入字符串, 所以使用标记位进行标记, 如果你的jdk是1.7及其以上, 那么可以不需要这个标记位, 而是直接传入字符进行判断.

一个字符串sf, 用于保存每次获取到的一个字符, 用于接下来的判断.

一个可变字符串sb, 用于保存单独的一整个数字.

四个计算和判断的方法:

截取字符进行判断操作的方法operating : 该方法每次截取一个字符, 如果是操作数, 那么存入可变字符串sb,当获取到了完整的一个独立数时,存入操作数集合中 ; 如果是操作符, 若不是反括号, 那么直接存入操作符集合, 若是反括号, 那么调用反括号的方法 ; 最后, 当输入的表达式截取完毕后, 判断, 如果操作数集合只剩下一个元素, 说明计算结果已经得到了, 结束方法即可, 否则, 调用计算方法进行四则运算.

四则运算计算方法operationStart : 根据获取到的两个操作数a , b一个操作符 op 进行判断对应的运算, 然后返回运算结果result .

为反括号的情况设计的特殊方法fanKuoHao : 因为是反括号, 意味着已经到了一对括号的尾部, 需要优先执行里面的表达式, 所以此时不把反括号压栈存储, 而是 直接取出两个操作数和一个操作符进行计算, 然后将结果压栈存储, 一直重复这个操作, 直到操作符栈顶元素是前括号时, 将前括号弹出, 然后结束方法.

弹出两个操作数和一个操作符的方法getTwoAndOne : 弹出两个操作数和一个操作符, 然后将参数传入四则运算计算方法operationStart , 得到计算结果后, 判断此时操作符栈顶的元素是否是减号, 如果是, 那么将结果取反然后压入操作数栈顶, 然后将操作符栈顶的减号改变为加号, 否则结束方法.

最后看一下大致的流程图:



注意: 根据这个算法只能进行简单的带括号的四则运算, 因为我自己也思考了其他的几种情况, 例如这些情况 : -(-(-(-(-(-16))))) , -(-5-2)-(-(-7)) , 这种表达式的难点就在于负号的判断, 头部的负号判断很容易, 其他位置的就有一定的难度了, 这部分功能就留给读者自己思考实现吧, 另外, 通常情况也不会计算这种”变态的表达式”.

最后代码如下(Java):

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/*
*      1.用String存储输入的算术表达式, 每次获取一个字符
*      2.判断获取到的字符, 以下几种情况依次处理:
*          如果是数字, 那么存入可变字符串;
*          如果是操作符,只要字符不是反括号 ")", 那么都压入对应的栈;
*          若字符串取完了, 则每次取两个操作数和一个运算符,计算后将结果压栈,如此循环,直到操作数栈剩一个数.
*      3.用两个集合模拟两个栈空间, 一个栈空间用于保存操作数, 另一个栈空间用于保存运算符
*      4.定义四则运算的方法
*      5.定义反括号特殊方法
*      6.定义操作字符串方法
*      7.定义弹出操作数和操作符的方法
*      8.由于测试机要求用jdk1.6, 而1.6中的switch语句还不支持String判断(1.7开始才支持),所以用代号代替运算符
*        +: 1      -: 2    *: 3    /: 4
*/
public class Main {
// 临时变量标记该运算类型
static int op = -1;
// 保存每次获取到的当前字符
static String sf = "";
//存储独立的数字
static StringBuffer sb = new StringBuffer();
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 操作数栈
List<Integer> operands = new ArrayList<Integer>();
// 运算符栈
List<String> operator = new ArrayList<String>();
// 字符串接收输入的字符
String str = sc.next();
// 调用判断的方法对该字符串进行操作
operating(str, operands, operator);
// 最后输出运算结果, 加入了一个判断头部是否是负号的判断
if (!
bbb3
operator.isEmpty() && operator.get(0).matches("[-]")) {
System.out.println(-operands.get(0));
} else {
System.out.println(operands.get(0));
}
}

// 操作字符串的方法
public static void operating(String str, List<Integer> operands, List<String> operator) {
// 每次截取一个字符进行判断
for (int i = 0; i < str.length(); i++) {
// 如果是数字那么存入可变字符串
sf = str.substring(i, i + 1);
//拼接数字
if (sf.matches("[0-9]")) {
sb.append(sf);
//下一个字符不是数字,或者这是最后一个数,那么存入集合,然后清空sb
if(i == str.length()-1 || !str.substring(i+1, i+2).matches("[0-9]")){
operands.add(Integer.parseInt(sb.toString()));
sb.delete(0, sb.length());
// 因为压入的是数字,所以判断前面的操作符是否是乘法*或除法/,如果是,那么取出两个操作数和一个操作符计算结果然后压入操作数栈
if (!operator.isEmpty() && operator.get(operator.size() - 1).matches("[/*//]")) {
getTwoAndOne(operands, operator);
}
}
} else if (sf.matches("[/+-/*(]")) { // 如果字符是除了数字和反括号以外的,那么直接压入操作符栈
operator.add(sf);
}else {                             // 如果字符是反括号,那么调用反括号的运行方法(即取出两个操作数和一个操作符的方法)
fanKuoHao(operands, operator);
}
}
// 字符串截取完毕, 两个栈里面剩下的都是+或者-的平级运算, 用循环运算完毕即可
while (true) {
// 操作数栈剩余一个数时, 结束算法
if (operands.size() == 1) {
break;
} else {
// 执行提取两个操作数和一个操作符的方法
getTwoAndOne(operands, operator);
}
}
}

// 四则运算方法,用switch语句进行判断,返回运算结果,传入两个操作数(注意先后顺序),和一个操作符
public static int operationStart(int a, int b, int op) {
int result = 0;
switch (op) {
case 1:
result = a + b;
break;
case 2:
result = a - b;
break;
case 3:
result = a * b;
break;
case 4:
result = a / b;
break;
default:
System.out.println("Error!");
}
return result;
}

/*
* 碰到反括号的运行方法: 碰到反括号, 直接取出两个操作数和一个操作符, 运算结果压入运算栈, 直到碰到运算符栈顶为前括号则结束方法
*/
public static void fanKuoHao(List<Integer> operands, List<String> operator) {
while (true) {
if (operator.get(operator.size() - 1).matches("[(]")) {
// 如果是前括号, 并且那么弹出前括号,并且结束方法
operator.remove(operator.size() - 1);
break;
} else {
getTwoAndOne(operands, operator);
}
}
}

/*
* 弹出两个数和一个操作符, 计算后将结果压入操作数栈
*/
public static void getTwoAndOne(List<Integer> operands, List<String> operator) {
// 判断操作符, 设置对应的op值
if (operator.get(operator.size() - 1).matches("[*]")) {
op = 3;
} else if (operator.get(operator.size() - 1).matches("[/]")) {
op = 4;
} else if (operator.get(operator.size() - 1).matches("[+]")) {
op = 1;
} else {
op = 2;
}
// 最后加减平级运算时,要考虑前一个数是不是负数,如果是,那么取反,并且下一个操作数变成+
int a = operands.get(operands.size() - 2);
int b = operands.get(operands.size() - 1);
if (operator.size() > 1 && operator.get(operator.size() - 2).matches("[-]")) {
a = -a;
operator.set(operator.size() - 2, "+");
}
int result = operationStart(a, b, op);
operands.remove(operands.size() - 1);
operands.remove(operands.size() - 1);
operands.add(result);
operator.remove(operator.size() - 1);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息