运用递归实现计算器加减乘除带括号优先级算法
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;
}
}
补充:利用数据结构里的逆波兰式能非常简洁的完成上述全部功能
我的思路:递归
先看下面这个算式:
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); } } }
补充:利用数据结构里的逆波兰式能非常简洁的完成上述全部功能
相关文章推荐
- C语言实现加减乘除(可以带括号,浮点数)计算器
- 简单加减乘除括号的计算器实现
- android(安卓)实现计算器程序,带优先级算法
- C++-----利用括号递归实现的加减乘除
- 简单四则运算计算器的C++实现(含括号和+-*/的优先级判断)
- 支持多位数,括号,四则运算,的计算器算法c++实现
- MFC 实现 加减乘除,括号,乘方的 计算器
- java实现四则运算,难点主要在理解加减乘除优先级以及使用递归
- Java实现带括号优先级的计算器
- 带括号的四则优先级运算的算法详细源码实现
- C语言实现计算器(包含加减乘除和括号)
- C#计算器(递归,WebService) 支持加减乘除括号等 (一)
- 运用PHP写计算器(实现加减乘除 取余数)功能
- C#计算器(递归,WebService) 支持加减乘除括号等 (二)
- 用C实现一个计算器(带小数点和括号以及运算优先级)
- [置顶] 使用C语言与栈实现简单多则运算计算器(包括括号优先级运算)
- 使用递归下降算法分析数学表达式 -- 基于堆栈的计算器实现算法
- 【LeetCode-面试算法经典-Java实现】【032-Longest Valid Parentheses(最长有效括号)】
- [算法运用研究]局部标准差实现对比度增强
- 20170219C++项目班02_02递归下降算法/解析器/Scanner实现