小白进阶 之 数据结构 - 栈
最近在看数据结构,其中栈的应用中有一个关于四则运算表达式求值。觉得有点意思,所以实现并记录之。
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
*/
public class LineStack{
private List data;
/**
* 无参 构造方法
*/
public LineStack() {
this.data = new ArrayList();
}
/**
* 有参 构造方法
* @param data
* @param count
*/
public LineStack(List 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 ls = new LineStack();
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 ls = new LineStack();
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 ls = new LineStack();
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实现——①栈-->栈的应用三、算术表达式求值