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

看了某些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);
		}
	}

}

补充:利用数据结构里的逆波兰式能非常简洁的完成上述全部功能

猜你喜欢

转载自blog.csdn.net/qq_37960007/article/details/78484144