逆波兰计算器----输入算式,实现整数的四则运算(包含小括号)

本文详细介绍了如何使用Java实现中缀表达式到后缀表达式的转换,以及通过后缀表达式进行计算的过程。通过栈数据结构,实现了中缀表达式的数字与运算符的识别,以及后缀表达式的生成。同时,文中还提供了比较运算符优先级的辅助类Priority。整个算法逻辑清晰,包括了括号处理、运算符优先级判断等关键步骤,最终能够计算出中缀表达式的值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在进行计算时,计算机与人不一样。相比较中缀表达式,它采用后缀表达式计算起来要明显方便许多。
下面就用 java + 栈 ,实现:对于输入的算式,转化为对应的后缀表达式,并且计算出算式值 的功能:

思路梳理

处理的主要思路为:

1、将输入的expression(中缀表达式)进行转换 => 中缀表达式对应的ArrayList类的list,

便于在由中缀表达式转换为后缀表达式的过程中,进行遍历操作:

1.1 需要注意的是:
* 1) 在转换的过程中,分为两种情况:
* 是运算符,直接add到list中;
* 是数字,可能是多位数,需要确定数字的所有位数之后,再add到list中
* 注意这里的循环控制条件中,不同模块之间的关系:
* ① 下标(索引)不越界
* >是运算符的情况,不需要加上这个,是因为只是if语句,不需要循环,外层的do-while循环就可以帮助实现–控制下标不越界
* >但是,是数字的情况,因为可能是多位数,所以有一个内层while循环;此时,就需要加上一个index < s.length()的条件控制下标不越界
* ② s.charAt(index)存储的是一个数字----ASCII值位于48~57之间,两点必须同时满足
*/
* 2) 输入的表达式中,数字 很可能是多位数,需要使用一个循环,确定多位数中的所有位数
* ----设置一个用于拼接的字符串str(初始值为 空串),在确定一个数的某一位上仍是数字时,将该位 拼接到str上
* 在一个数的位数的确定过程中,如果某一位上是 运算符,就将之前的str,add到list中去

2、将中缀表达式对应的list转换为对应的后缀表达式
  • 2.1 理论上需要两个栈:一个符号栈s1,一个用于存储确定的运算顺序的栈s2;并且最后s2栈的逆序输出结果就是逆波兰表达式(后缀表达式)
    * 但是,因为s2在整个过程中,并不进行pop()操作;并且最终的逆波兰表达式 是 s2中的元素逆序输出的结果
    * ----实际上,可以使用ArrayList代替stack,来作为s2对象真实的数据结构
    * 2.2 在判断读取到的c(list中的元素),是数字还是符号的过程中,
    * 可以选择正则表达式:matches("\d+")----表示:多位数
    * 2.3 比较运算符优先级的方式,需要自己来实现
    * 可以来定义一个类priority,来实现 确定某个运算符的优先级

①实现将中缀表达式对应的list转换为对应的后缀表达式,这里的算法实现比较复杂:

  • 1、需要两个栈:一个符号栈s1,一个用于存储确定的运算顺序的栈s2;并且最后s2栈的逆序输出结果就是逆波兰表达式(后缀表达式)
    * 2、从左往右遍历list(存储的是expression的中缀序列)
    * 3、如果读取到的c是 数字,add进s2;
    * 4、如果读取到的c是 运算符,分为以下几种情况:
    * 1> 如果s1是空栈,or s1的栈顶元素是 左括号“(”,将c push进s1;
    * 2> 如果c的优先级 大于 s1的栈顶元素的优先级,那么将c push进s1;
    * 3> 如果c的优先级 小于或者等于 s1的栈顶元素的优先级,那么,将s1 的栈顶元素出栈,add 进s2中去;
    * 并且,再次转回4-1>,重新判断c 与 s1的状态;
    * 5、 如果读取到的c是 括号,分为以下情况
    * 1> 如果c是左括号“(”,那么,直接push进入s1;
    * 2> 如果c是右括号“)”,那么,需要将s1的栈顶元素pop,并且add进入s2,直至遇见左括号“(”为止
    * ----这也是消除一对左括号的方法;
    * 6、重复上述2~5步骤,直至将list的最后一个元素读取结束
    * 7、因为s2是采用的ArrayList,所以,最终s2中存储的,就是转换之后的 逆波兰表达式

②在实现时的注意事项:

  • 将中缀表达式对应的list转换为对应的后缀表达式
    * 2.1 理论上需要两个栈:一个符号栈s1,一个用于存储确定的运算顺序的栈s2;并且最后s2栈的逆序输出结果就是逆波兰表达式(后缀表达式)
    * 但是,因为s2在整个过程中,并不进行pop()操作;并且最终的逆波兰表达式 是 s2中的元素逆序输出的结果
    * ----实际上,可以使用ArrayList代替stack,来作为s2对象真实的数据结构
    * 2.2 在判断读取到的c(list中的元素),是数字还是符号的过程中,
    * 可以选择正则表达式:matches("\d+")----表示:多位数
    * 2.3 比较运算符优先级的方式,需要自己来实现
    * 可以来定义一个类priority,来实现 确定某个运算符的优先级
    */
3、对于expression调整得到的list,进行遍历,并且结合堆栈,完成运算calculation
  • 3.1 注意的是:在进行判断 集合中的元素是数字(包含多位数的情况)还是运算符的时候,是可以使用正则表达式
    * 对应的是string类的matches(regex)方法,其中的regex表示:正则表达式
代码实现

1、实现主要功能的类PolandNotation( 包含mian() )

class PolandNotation {
	public static void main(String[] args) {
		
		//创建一个string对象,存储要进行计算的表达式
		String expression = "1+((2*4+3)*4)-5";
		//将输入的expression(中缀表达式)进行转换 => 中缀表达式对应的ArrayList类的list
		List<String> list = infixException(expression);
//		System.out.println(list);
		List<String> suffixExpression = suffixExpression(list);
//		System.out.println(suffixExpression);
		int res = calcultation(suffixExpression);
		System.out.println(expression + " 的计算结果为: " + res);
		
	}
	
	//将输入的expression(中缀表达式)进行转换 => 中缀表达式对应的ArrayList类的list
	public static List<String> infixException(String s){
		//创建一个ArrayList类的对象,存储中缀表达式对应的list
		ArrayList<String> list = new ArrayList<String>();
		//对于s中的元素进行遍历,来达到转换的目的
		char c;//暂存 从s中读取到的,需要进行判断的字符
		String str;//用于:在读取数的过程中,暂存多位数的string形式的变量
		int index = 0;//表示s中,表示当前 遍历到的索引下标
		do {
			c = s.charAt(index);
			//如果c是运算符,那么c对应的ASCII值,应该就不在[48,57]的范围之内。注意:判断是不是运算符时,对于ASCII的范围,一定不包含 == 的情况 
			if(c > 57 || c < 48) {
				list.add("" + c);//因为 c是char型得到,所以应该转换为string类型(拼接一个 空串"")
				index ++ ;//下一个的索引值
			}else {
				//c是数字的情况:每一位上面的数字就一定要在[48,57]的范围之内,可能是多位数,需要确定数字的所有位数之后,再add到list中
				str = "";//初始化str
				while(index < s.length() && (s.charAt(index) <= 57 && s.charAt(index) >= 48)) {
					c = s.charAt(index);
					/*
					 * 注意,在内层的while循环处:c = s.charAt(index),这个赋值是一定要存在的。
					 * 虽然在if的上一句也存在这个赋值语句,但是,只对内层while循环的第一次进行有用。
					 * 如果不在内层的while循环处,加上一个这样的赋值语句,就会发现,c的值,一直是1
					 */
					str += c;
					index ++;//指向下一个索引
				}
				list.add(str);
			}
		}while(index < s.length());

		return list;
	}
	
	//将中缀表达式对应的list转换为对应的后缀表达式
	public static List<String> suffixExpression(List<String> list) {
		//创建s1、s2
		Stack<String> s1 = new Stack<String>();
		ArrayList<String> s2 = new ArrayList<String>();
		//循环遍历list中的元素
		for(String item : list) {
//			System.out.println("item=" + item);
			if(item.matches("\\d+")) {
				//2、item如果是数字,直接add进s2
				s2.add(item);
			}else if(item.equals("(")){
				//5.1 是左括号,直接push进入s1
				s1.push(item);
			}else if(item.equals(")")) {
				/*
				 * 5.2 是右括号“)”,那么,需要将s1的栈顶元素pop,并且add进入s2,直至遇见左括号“(”为止
				 * ----这也是消除一对左括号的方法;
				 */
				while(!(s1.peek().equals("("))) {//直至遇见左括号“(”
					s2.add(s1.pop());
				}
				/*
				 * 注意:这一步很重要!
				 * 因为在遇到右括号时,有两项任务是要完成的:
				 * >需要将s1的栈顶元素pop,并且add进入s2,直至遇见左括号“(”为止
				 * >消除一对左括号:----当对s1执行pop操作,直到遇到s1.peek(s1的栈顶元素)为左括号时,应该停止s2.add(s1.pop())操作,并且执行s1.pop()----将左括号从s1中pop出来,但是 不add进s2,:实现丢弃一对括号
				 */
				s1.pop();//很重要!!!
			}else {
				/*
				 *是4、的所有情况,写在一起处理 
				 * 这里在进行处理时,最简单的方法,是在这一小块时,把左括号也当做运算符,且优先级最低
				 * 这是因为:4.1--不论读取到的运算符是什么(+-*或除),在遇到左括号时,都进行操作s1.push()
				 */
					while(s1.size() > 0 && Priority.getPriority(s1.peek()) >= Priority.getPriority(item)) {
						/*
						 * 基本上对应4.3:
						 * 实际上,是在item是运算符时,除了4.1、4.2之外的所有情况
						 */
						s2.add(s1.pop());//将s1的栈顶元素取出,add进s2中去
					}
					/*
					 * 对应4.1,4.2的情况:
					 * 1> 如果s1是空栈,or s1的栈顶元素是 左括号“(”,将c push进s1;
					 * 2> 如果c的优先级 大于 s1的栈顶元素的优先级,那么将c push进s1;
					 */
					s1.push(item);
			}	
		}
		//从左至右扫描结束后,将s1中的剩余元素全都pop,再add进s2
		while(s1.size() > 0) {//s1中没有空
			s2.add(s1.pop());
		}
//		System.out.println(s2);
		return s2;
	}
	
	
	//对于expression调整得到的list,进行遍历,并且结合堆栈,完成运算calculation
	public static int calcultation(List<String> ls) {
		
		//创建一个堆栈的对象,在计算过程存储数字,也称为数栈
		Stack<String> stack = new Stack<String>();
		
		/*
		 * 开始对ls进行遍历,具体的操作为:
		 * 如果遇到 数字,就将数字压入堆栈;
		 * 如果遇到 运算符,就从数栈中pop()出来栈顶元素、次顶元素:num1、num2。
		 * 进行相应运算,并将运算的结果压入数栈
		 */
		for (String item : ls) {

			// 如果是数字(多位数),直接入 数栈
			if (item.matches("\\d+")) {// 是多位数
				stack.push(item);
			} else {
				/*
				 * 是运算符
				 * 出栈2个元素:栈顶元素、次顶元素
				 */
				//定义一个变量,暂存每次计算的结果值;并在最终,将最终的结果值赋给他

				int num2 = Integer.parseInt(stack.pop());// 栈顶元素
				int num1 = Integer.parseInt(stack.pop());// 次顶元素
				int res = 0;
				// 根据当前的item的运算符类型,进行相应的计算
				switch (item) {
				case "+":
					res = num1 + num2;
					break;
				case "-":
					res = num1 - num2;
					break;
				case "*":
					res = num1 * num2;
					break;
				case "/":
					res = num1 / num2;
					break;
				default:
				    throw new RuntimeException("输入的运算符有误!");
				}
				stack.push(res + "");//因为res是int类型的,转换为string类型的才可以入栈	
			}	
		}
		return Integer.parseInt(stack.pop());	
	}
}

2、在实现中缀表达式 转为对应的后缀表达式时,确定运算符优先级的类

/*
 * 比较运算符优先级的方式,需要自己来实现
 * 可以来定义一个类priority,来实现 确定某个运算符的优先级
 */
class Priority{
	//定义几个属性(全局常量),来表示各个 运算符对应的优先级
	private static final int SUM = 1;
	private static final int DEC = 1;
	private static final int MUL = 2;
	private static final int DIV = 2;
	
	public static int getPriority(String operation) {//传入的是一个运算符
		int res = 0;//优先级默认值为0
		//确定传入的运算符的优先级
		switch(operation) {
		case "+":
			res = SUM;
			break;
		case "-":
			res = DEC;
			break;
		case "*":
			res = MUL;
			break;
		case "/":
			res = DIV;
			break;
		case "(":
			//默认左括号----“(”的优先级最低,即为res的默认值--0
			break;
		default:
			//对应的是s1.peek().equals("(")
			System.out.print("输入的运算符有误!!!" + "\t");
		}
//		System.out.println(operation);
		return res;
	}
	
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值