【数据结构 - 栈的应用】四则运算表达式求值 java实现

小白进阶 之 数据结构 - 栈

最近在看数据结构,其中栈的应用中有一个关于四则运算表达式求值。觉得有点意思,所以实现并记录之。

20世纪50年代,波兰逻辑学家Jan Łukasiewicz想到了一种不需要括号的后缀表达法,即逆波兰(Reverse Polish Notation)表示,这一表示方式,巧妙地解决了程序实现四则运算的难题[1]。

【问题】对一标准的四则运算表达式进行运算求值。

【分析】标准的四则运算表达式,如 9+(3-1)*3+10/2,这也就是中缀表达式。通常程序中四则运算使用其转化成后缀表达式:9 3 1 - 3 * + 10 2 / +,然后再进行运算。

【思路】使用栈实现四则运算

一般可以将算术表达式求值的过程分为两个过程:
    1. 中缀表达式  -->  后缀表达式                                       
         思路:从左到右遍历中缀表达式的每个数字和符号                               
            若是数字,直接添加到后缀表达式后边(或保存到某一链表中)
            若是符号,则判断其与栈顶符号的优先级
                是 右括号 或 优先级 不高于 栈顶符号的优先级,则栈顶元素依次出栈并输出,直到栈顶元素为左括号
                并 将当前符号进栈,一直到最终输出后缀表达式为止。
                                                           
                    注意:栈顶元素与当前符号优先级相同也要输出!
    2. 后缀表达式进行四则运算
      思路:把数字压入堆栈,遇到操作符就从栈中取出两个数进行相关运算,把结果在存放入栈中直到最后操作完成,输出最终结果

【步骤分析】

   

【实现】

    1. 栈结构

package datastructure_01.stack;

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

/**
 * 栈结构
 * @author Admin
 *
 * @param <T>
 */
public class LineStack<T>{
	
	private List<T> data;

	
	/**
	 * 无参 构造方法
	 */
	public LineStack() {
		this.data = new ArrayList<T>();
	}

	/**
	 * 有参 构造方法
	 * @param data
	 * @param count
	 */
	public LineStack(List<T> data) {
		this.data = data;
	}
	
	/**
	 * 压栈
	 * @param e 要进栈的元素
	 */
	public void push(T e) {
		// 不需要判断是否已满
		this.data.add(e);
	}
	
	/**
	 * 弹栈
	 * @return 返回栈顶元素
	 */
	public T pop() {
		if(isEmpty()) {
			throw new RuntimeException("栈空,无法弹栈~");
		}
		return this.data.remove(this.data.size() - 1);
	}
	
	/**
	 * 获取栈顶元素
	 * @return 返回栈顶元素
	 */
	public T top() {
		if(isEmpty()) {
			throw new RuntimeException("栈空,无法获取栈顶元素~");
		}
		return this.data.get(this.data.size() - 1);
	}
	
	/**
	 * 判断栈空
	 * @return 布尔值
	 */
	public boolean isEmpty() {
		if(this.data.size() == 0) {
			return true;
		}
		return false;
	}
	
	/**
	 * 线性栈的演示
	 * 	简单的功能测试
	 */
	public static void main(String[] args) {
		LineStack<Integer> ls = new LineStack<Integer>();
		System.out.println(ls.top());
//		ls.push(5);
//		System.out.println(ls.pop());
//		System.out.println(ls.isEmpty());
		//System.out.println(ls.top());
	}

}

    2. 四则运算的两个操作过程及其他辅助判断

package datastructure_01;

import datastructure_01.stack.LineStack;

/**
 * 四则运算的两个操作过程及其他辅助判断
 * 
 * 使用栈实现四则运算
 * 	1.中缀表达式  -->  后缀表达式
 * 	     思路:从左到右遍历中缀表达式的每个数字和符号
 * 			若是数字,直接输出(或保存到某一链表中)
 * 			若是符号,则判断其与栈顶符号的优先级
 * 				是 右括号 或 优先级不高于 栈顶符号的优先级,则栈顶元素依次出栈并输出
 * 				并 将当前符号进栈,一直到最终输出后缀表达式为止。
 * 
 * 					注意:栈顶元素与当前符号优先级相同也要输出!
 * 	2.后缀表达式进行四则运算
 * 	  思路:把数字压入堆栈,遇到操作符就从栈中取出两个数进行相关运算,把结果在存放入栈中直到最后操作完成,输出最终结果
 * @author Admin
 *
 */
public class ArithmeticExp{
	// 成员变量
	private String prefixExp;     // 前缀表达式
	private String infixExp;      // 中缀表达式
	private String postfixExp;    // 后缀表达式
	
	/**
	 * 构造方法
	 */
	public ArithmeticExp() {}
	
	public ArithmeticExp(String infixExp) {
		this.infixExp = infixExp;
	}
	
	// Setters & Getters
	public String getPrefixExp() {
		return prefixExp;
	}
	public void setPrefixExp(String prefixExp) {
		this.prefixExp = prefixExp;
	}
	public String getInfixExp() {
		return infixExp;
	}
	public void setInfixExp(String infixExp) {
		this.infixExp = infixExp;
	}
	public String getPostfixExp() {
		return postfixExp;
	}
	public void setPostfixExp(String postfixExp) {
		this.postfixExp = postfixExp;
	}
	
	/**
	 * 1.转化成后缀表达式(逆波兰表达式)
	 * 
	 */
	public void cover2PostfixExp(){
		
		// 创建堆栈
		LineStack<Character> ls = new LineStack<Character>();
		this.postfixExp = "";
		
		// 遍历表达式的每一个字符
		for(int i = 0; this.infixExp != null && i < this.infixExp.length();i++) {
			char ch = this.infixExp.charAt(i);
			if(' ' != ch) {	// 当前字符不为空 时的操作
				if(isLeftBracket(ch)) {	// 是左括号,压栈
					ls.push(ch);
				} else if(isRightBracket(ch)) { // 是右括号,将所有操作符出栈,直到遇到一个左括号,并将这个左括号丢弃
					char topOperator = ls.pop();
					while(!isLeftBracket(topOperator)) {
						postfixExp += (topOperator + " ");	// 使用空格 隔开
						topOperator = ls.pop();
					}
				} else if(isOperator(ch)) {	// 是操作符,要判断优先级,再决定是否需要入栈
					/**
					 * 如果栈为空,直接进栈。 如果栈非空,则需要将栈顶运算符的优先级和要入栈的运算符的优先级进行比较
					 * 	将栈中比要入栈的运算符优先级高的运算符都出栈,然后将该运算符入栈
					 */
					if(!ls.isEmpty()) { // 如果栈非空
						Character topOperator = ls.top();
						while(topOperator != null && priority(topOperator.charValue()) >= priority(ch)) {
							postfixExp += (ls.pop() + " ");
							if(!ls.isEmpty()) {
								topOperator = ls.top();
							}else {
								break;
							}
						}
					}
					
					// 将当前操作符 压栈
					ls.push(ch);
				} else {
					if(i > 0 && isNumber(infixExp.charAt(i - 1))) {
						postfixExp = postfixExp.substring(0, postfixExp.length() - 1) + ch + " ";
					} else {
						postfixExp += ch + " ";
					}
				}
				
			}
		}
		
		while(!ls.isEmpty()) {
			postfixExp += (ls.pop() + " ");
		}
		
        // 去除表达式中的最后一个空格
//		postfixExp = postfixExp.substring(0, postfixExp.length() - 1);
		postfixExp = postfixExp.trim();
	}

	/**
	 * 运算符优先级比较
	 * @param charValue
	 * @return
	 */	
	public int priority(char charValue) {
		switch(charValue) {
		case '+':
		case '-': 
			return 1;
		case '*':
		case '/':
		case '%': 
			return 2;
		case '^':
			return 3;
		}
		return 0;
	}

	/**
	 * 判断是否是操作符
	 * @param ch
	 * @return
	 */
	public boolean isOperator(char ch) {
		if(ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '^' || ch == '%') {
			return true;
		}
		return false;
	}

	/**
	 * 2.使用后缀表达式 进行 四则运算
	 */
	public int calculateExpResult() {
		String[] strs = this.postfixExp.split(" ");
		LineStack<Integer> ls = new LineStack<Integer>();
		int result = 0;
		for(int i = 0; i < strs.length; i++) {
			
			// 如果是操作符,从堆栈获取两个值,进行运算
			if(strs[i].length() == 1 && isOperator(strs[i].charAt(0))) {
				int num2 = ls.pop();
				int num1 = ls.pop();
				ls.push(calculate2Numbers(num1,num2,strs[i].charAt(0)));
			} else {	// 如果是数字,压入堆栈
				ls.push(Integer.parseInt(strs[i]));
			}
		}
		return ls.pop();
	}
	
	/**
	 * 两数的运算操作
	 * @param num1
	 * @param num2
	 * @param operator
	 * @return
	 */
	public Integer calculate2Numbers(int num1, int num2, char operator) {
		switch(operator) {
		case '+':
			return num1 + num2;
		case '-':
			return num1 - num2;
		case '*':
			return num1 * num2;
		case '/':
			return num1 / num2;
		case '%':
			return num1 % num2;
		case '^':
			return num1 ^ num2;
		}
		return null;
	}

	/**
	 * 判断是否是左括号
	 * @param ch
	 * @return 布尔值
	 */
	public boolean isLeftBracket(char ch) {
		if(ch == '(') {
			return true;
		}
		return false;
	}
	
	/**
	 * 判断是否是右括号
	 * @param ch
	 * @return 布尔值
	 */
	public boolean isRightBracket(char ch) {
		if(ch == ')') {
			return true;
		}
		return false;
	}
	
	/**
	 * 判断是否是数字
	 * @param ch
	 * @return
	 */
	public boolean isNumber(char ch) {
		if(ch >= '0' && ch <= '9') {
			return true;
		}
		return false;
	}
	
}


    3. 实现四则运算求值的演示

package datastructure_01;

import java.util.Scanner;

/**
 * 四则运算求值的演示
 * @author Admin
 *
 */
public class ArithmeticExpressionDemo {
	/**
	 * 主方法
	 * @param args
	 */
//	中缀表达式:9+(3-1)*3+10/2
	public static void main(String[] args) {
		// 从控制台获取中缀表达式
		Scanner sc = new Scanner(System.in);
		
		String infixExp = sc.nextLine();
		sc.close();
		
		// 创建对象,进行后续操作
		ArithmeticExp ae = new ArithmeticExp(infixExp);
		System.out.println("中缀表达式:"+ae.getInfixExp());
		
		// 转化为后缀表达式
		ae.cover2PostfixExp();
		
		System.out.println("后缀表达式:"+ae.getPostfixExp());
		System.out.println(ae.getPostfixExp().length());
		
		// 使用后缀表达式 进行 四则运算
		int calculateExpResult = ae.calculateExpResult();
		
		// 结果输出
		System.out.println("计算结果:"+calculateExpResult);
		
	}

}

    4. 结果

【总结】

RPN求值过程的特点:

(1)所有的操作数的顺序不会改变;

(2)运算符的顺序可能会变。

目前考虑的一些情况较少,比较有局限性,有待完善。


参考:[1] 程杰. 大话数据结构[M], p105. 

           [2] 数据结构Java实现——①栈-->栈的应用三、算术表达式求值

 

  • 6
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值