您的位置:首页 > 其它

运用递归实现计算器加减乘除带括号优先级算法

2017-11-08 22:26 513 查看
看了某些csdn博客的计算器带括号优先级算法并不能很好地处理复杂的算式。这里自己就写了一个可以处理复杂算式的算法。

我的思路:递归
先看下面这个算式:
String s5="(343+5757/3*(787-45+780)-9*233+(787+909-(32*7))*555/(7+88*565-99))";

运算结果:



如何让算法智能地算出这个字符串的结果呢?
先想想我们在碰到这种算式时是怎么开始的,首先,我们会找到这个算式的优先级,比如我们更希望先得到787-45+780的结果,再让这个结果乘上3,同样,我们更希望得到32*7的结果后再让909减去它。所以同样的道理,如果有n级括号,我们理所当然地希望先得到最里面的结果,再把结果返回给上一层,并参与上一层的运算

比如下面的例子:


按照上面的流程,我们只需从第5层开始,按照箭头的逆方向分别返回结果并和上一层的数字相运算,以此类推,最终我们便能得到结果。

那么我们该怎么开始呢?虽然我们看流程已经知道可以用递归实现,但是我们应该怎么选择,怎么封装,需要哪些工具方法才能帮助我们实现呢?我们发现,如果仅仅是用递归处理字符串是完全不够的,因为至少一个字符串不能智能地拆分成多个子字符串,那么有人就会想可以定义一个方法处理字符串得到多个子字符串,但是这样的话参数又不匹配了,传的是一个String,但是我们得到的却又是String[],那么有人又说可以把一个String装成数组传进去,再用方法得到多个String,再。。。好吧,虽然可能能实现,但是肯定会比较复杂,说不定人就晕了。

选择方式:如果我们仔细观察上面的流程话就会发现有些类似的地方-----java的File类
每一个File都能listFile出一堆File,这不正好和流程图有着异曲同工之妙吗?所以我们第一要做的就是封装类

这里自己写了个类叫做WiseString,意为智能的字符串,下面先看WiseString类一个主要的函数,该函数用来把一个WiseString按加减法或者乘除法分割成同等级的若干 子WiseString(即表达式,如把(3+4-5)/3+2*6*7-999分成(3+4-5)/3,2*6*7和999)



 






当然我们知道一个复杂的表达式里面是加减乘除混杂的,我们根本不知道运行的时候什么时候遇到加减,什么时候遇到乘除,但是subString这个函数添加了一个flag,用来区分是以加减法分割字符串还是以乘除法分割字符串。其实,如果我们仔细观察会发现,一旦把一个字符串按加减法分割后,得到的子字符串都是乘除表达形式的整体,反之,如果以乘除法分割字符串,得到的子字符串都是加减法表达式形式的整体,(比如(3+4-5)/3+2*6*7-999这个字符串,我们一开始以加减法分割,那么得到的子字符串我们就要得用乘除法分割了),所以综合看来就是一个加减法和乘除法交替处理字符串的过程,那么通过让flag对2取余就正好可以做到这点。

加减乘除括号等符号的保存:
既然处理了字符串的分割,那么怎么保存那些运算符号呢?其实很简单,用一个ArrayList<String>就行了,在得到字符串的时候就把运算符号按顺序保存起来




有些人输入的时候会调皮,怎么解决?
比如一个简单的表达式(8-5)-8*9,有些人偏要多加几个括号:((((8-5)-8*9))),那么如果不处理程序就会出错,所以这里需要解决一下,这里写了一个函数:formString,属于WiseString的成员函数




下面来看看怎么用递归处理字符串







那么整个大概的算法差不多就是这些了,其实将算法运用到web里面也是个不错的想法:下面就附几张图:







当然,算法还有缺陷,就是不能保存结果为小数,比如最开始的结果2918980实际上是2918980.46

最后,附上源代码,有不足的地方欢迎指出:
package com.utils;

import java.util.ArrayList;

public class WiseString {

private static boolean hasLoadFirst = false;

String str = "";
public static int flag = 0;

public WiseString(String str) {
this.str = str;
}

/*public static boolean hasLoadFirst() {
return hasLoadFirst;
}*/

public void equal(WiseString ws) {
this.str = ws.str;
}

WiseString[] fomatPlus() {
return subString(1);
}

WiseString[] fomatMul() {
return subString(0);
}

WiseString[] subString(int flag) {
String s = str;
if (s == null) {
return null;
}
// 这里有两种分割字符串的方式
// 用来区分是以加减法分割还是以乘除法分割
char mark1;
char mark2;
// 以加减法分割
if (flag == 1) {
mark1 = '+';
mark2 = '-';
} else if (flag == 0) {
// 以乘除法分割
mark1 = '*';
mark2 = '/';
} else {
return null;
}
// 如果一个字符串里不含+,-,*,/那么说明这个字符串已经不可分割了,直接返回
if (!s.contains(mark1 + "") && !s.contains(mark2 + "")) {
return new WiseString[] { this };
}

// +-符号的位置集合
ArrayList<Integer> locat_plus = new ArrayList<Integer>();
// ( 的位置集合
ArrayList<Integer> locat_left = new ArrayList<Integer>();
// ) 的位置集合
ArrayList<Integer> locat_right = new ArrayList<Integer>();

char[] cs = s.toCharArray();
// 用来记录左括号的数量
int left = 0;
// 用来记录右括号的数量
int right = 0;
int cs_length = cs.length;
// 记录左右括号在字符串的位置
for (int i = 0; i < cs_length; i++) {
char c = cs[i];
if (c == mark1 || c == mark2) {
locat_plus.add(i);
}
if (c == '(') {
left++;
if (left - right == 1) {
locat_left.add(i);
}
}
if (c == ')') {
right++;
if (left == right) {
locat_right.add(i);
}
}
}
// 我们知道对于这种字符串:(aa*sss)-(yyy+ass)
// 我们希望把他分成两份,所以要以中间的-号为分界线
// 那么我们就需要记录-号的位置
// 但同时我们不希望记录*号和+号的位置
// 所以身在括号里的符号需要remove掉
ArrayList<Integer> needRemove = new ArrayList<Integer>();
// 遍历所有的符号,移除掉身在括号里的符号
for (int i = 0; i < locat_plus.size(); i++) {
int location_plus = locat_plus.get(i);
for (int j = 0; j < locat_left.size(); j++) {
if (locat_left.get(j) < location_plus
&& location_plus < locat_right.get(j)) {
needRemove.add(location_plus);
}
}
}
// 开始移除
for (int i = 0; i < needRemove.size(); i++) {
locat_plus.remove(needRemove.get(i));
}

// 可能+-*/号全部在()里面,那么remove后List为空,为了防止空指针需要返回
// 同时,如果所有的符号都在括号里,此时就代表该字符串是一个整体,为了保持
// 一层分一级的思路,我们需要返回这个整体,比如(1+3*9-2)
if (locat_plus.size() == 0) {
WiseString[] s_ = new WiseString[1];
s_[0] = new WiseString(s);
return s_;
}
// 开始以剩下的符号位置分割字符串
String s0 = "";
for (int i = 0; i < locat_plus.get(0); i++) {
s0 += cs[i];
}
// marks.add(cs[i_+1]+"");

int length = locat_plus.size();
String[] centerString = new String[length + 1];
WiseString[] centerWiseString = new WiseString[length + 1];
initStrings(centerString);
for (int i = 0; i < length - 1; i++) {
// ----------------------------------------------------------+1
for (int j = locat_plus.get(i) + 1; j < locat_plus.get(i + 1); j++) {
centerString[i + 1] += cs[j];
}
// marks.add(cs[i_+1]+"");
centerWiseString[i + 1] = new WiseString(centerString[i + 1]);
}

String s_last = "";
for (int i = locat_plus.get(length - 1) + 1; i < cs_length; i++) {
s_last += cs[i];
}

centerWiseString[0] = new WiseString(s0);
centerWiseString[length] = new WiseString(s_last);

return centerWiseString;
}

String fomatString() {
if (str == null) {
return "";
}
char[] cs = str.toCharArray();
int length = cs.length;
ArrayList<Integer> locatLeft = new ArrayList<Integer>();
ArrayList<Integer> locatRight = new ArrayList<Integer>();
int left = 0;
int right = 0;
for (int i = 0; i < cs.length; i++) {
char c = cs[i];
if (c == '(') {
left++;
if (left - right == 1) {
locatLeft.add(i);
}
}
if (c == ')') {
right++;
if (left == right) {
locatRight.add(i);
}
}
}

if (cs[0] == '(' && cs[length - 1] == ')' && locatRight.size() == 1) {
str = str.substring(1, length - 1);
str = fomatString();
}
return str;
}

private static void initStrings(String[] ss) {
if (ss == null) {
return;
}
for (int i = 0; i < ss.length; i++) {
ss[i] = "";
}
}

public String toString() {
return str;
}

}

public class Calcu {
private int flag_ = -1;
public ArrayList<String> marks = new ArrayList<String>();

// 读取字符串的运算符号
public void load(String s) {
if (s == null) {
return;
}
char[] cs = s.toCharArray();
for (int i = 0; i < cs.length; i++) {
char c = cs[i];
if (c == '+' || c == '-' || c == '*' || c == '/') {
marks.add(c + "");
}
}
}

// 得到运算符号
public String getMark() {
flag_++;
return marks.get(flag_);
}

public int getIntResult(WiseString ws, int flag) throws Exception {
// 去除多余的括号
ws.fomatString();
// 读取运算符号
load(ws.str);
// 得到计算结果
int result = getIntResult_(ws, flag);

return result;
}

private int getIntResult_(WiseString ws, int flag) throws Exception {
if (ws == null) {
throw new Exception("WiseString Is Null");
}
int sum = 0;
ws.fomatString();
WiseString[] wss = ws.subString(flag);
// 如果一个字符串分割后不变,说明已经不可分割,需返回
if (wss[0].equals(ws)) {
return Integer.parseInt(wss[0].str);
}
int length = wss.length;
int nextFlag = ++flag % 2;
ArrayList<Integer> ints_ = new ArrayList<Integer>();
ArrayList<String> mark_ = new ArrayList<String>();
for (int i = 0; i < length; i++) {
wss[i].fomatString();
// 这里之所以要有一个i!=0的条件是因为n个表达式只需要n-1个符号运算
if (i != 0) {
// 保存相应的运算符号,比如+和-两个
mark_.add(getMark());
}
int result = getIntResult_(wss[i], nextFlag);
// 保存相应需要运算的数字,比如111,222,333
ints_.add(result);
}
// 将运算符和数字结合---111+222-333,并算出结果
sum = calcu(mark_, ints_);

return sum;

}

private int calcu(ArrayList<String> mark_, ArrayList<Integer> ints_) {
int sum = 0;
int mark_length = mark_.size();
int ints_length = ints_.size();

if (mark_length != 0 && ints_length != 0
&& ints_length - mark_length == 1) {

for (int i = 0; i < ints_.size(); i++) {
if (i == 0) {
sum = ints_.get(i);
} else {
String mark = mark_.get(i - 1);
if (mark.equals("+")) {
sum += ints_.get(i);
}
if (mark.equals("-")) {
sum -= ints_.get(i);
}
if (mark.equals("*")) {
sum *= ints_.get(i);
}
if (mark.equals("/")) {
sum /= ints_.get(i);
}
}
}
return sum;
} else {
return ints_.get(0);
}
}

}

补充:利用数据结构里的逆波兰式能非常简洁的完成上述全部功能
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: