中缀表达式计算器——Java实现,有简单图形界面
前言:
本篇文章撰写的初衷是为了记录本人的学习以及便于本人的再次学习,并为感兴趣的读者提供一个简单的思路参考。由于本人的能力有限,所以出现错误之处尽请见谅,如若能指出错误之处,笔者不胜感激。
问题描述:
给定一个中缀表达式,将中缀表达式转换为后缀表达式(逆波兰表达式),并计算出逆波兰表达式的值。
本逆波兰计算器限制:
- 支持多位整数,不支持小数(对于支持小数的代码编写有兴趣的读者可以自行思考)
- 支持操作符: ‘+’, ‘-’, ‘*’, ‘/’, ‘(’, ‘)’
逆波兰计算器算法:
一、中缀表达式转换为后缀表达式(逆波兰表达式)算法:
- 初始化两个栈,一个为存储操作符的操作符栈,一个为存储逆波兰表达式的逆波兰表达式栈。
- 从左至右扫描中缀表达式。
- 若读取到的字符为数字,则向后分析直到数字串的结束,并将数字串压入逆波兰表达式栈。
- 若读取到的字符为’(’,则直接将’(‘压入操作符栈,该操作符只有遇到右括号’)'的时候出栈。
- 若读取到的字符为’)’,则将操作符栈中的栈顶操作符依次出栈并压入逆波兰表达式栈,直到遇到左括号’(‘为止,将操作符栈中栈顶的左括号’('出栈。
- 若读取到的字符为运算符’+’, ‘-’, ‘*’, ‘/’:
a. 如果操作符栈为空或栈顶元素为’(’,则将读取到运算符直接压入操作符栈;
b. 如果读取的运算符的优先级高于操作符栈栈顶运算符的优先级,则将读取的运算符直接压入操作符栈;
c. 如果读取的运算符的优先级低于或等于操作符栈栈顶运算符的优先级,则将操作符栈栈顶运算符出栈并压入逆波兰表达式栈,将读取到的运算符压入操作符栈。(注意,此处优先级的比较是不断比较操作符栈栈顶运算符的优先级直到读取到的运算符的优先级高于操作符栈栈顶运算符的优先级或遇到’('或栈空)。- 重复2-6操作直到输入的中缀表达式扫描完毕,若操作符栈中仍然存在运算符,则将操作符栈中栈顶的运算符依次出栈并压入逆波兰表达式栈直到操作符栈为空。
- 逆波兰表达式栈的出栈元素的逆序为中缀表达式转换后的后缀表达式(逆波兰表达式)。
二、后缀表达式(逆波兰表达式)求值算法:
- 初始化一个操作数栈。
- 从左至右依次扫描后缀表达式的单元。
- 如果扫描的单元是数字串,则将其转换为数字,并压入操作数栈,并扫描下一个单元。
- 如果扫描的单元是运算符,则对操作数栈栈顶上的两个操作数执行该运算(取两次栈顶),并将运算结果重新压入操作数栈。
- 重复2-4操作,直到后缀表达式单元扫描完毕,最终操作数栈中栈顶元素即为计算结果值。
测试举例:
输入中缀表达式:10+((20+30)*40)-50 (注意输入为英文字符)
求得后缀表达式:10 20 30 + 40 * + 50 - (中间用空格隔开)
后缀表达式计算结果:1960
中缀表达式转后缀表达式过程分析:
扫描到的字符 | 操作符栈(栈底->栈顶) | 逆波兰表达式栈(栈底->栈顶) | 说明 |
---|---|---|---|
1 | 扫描到为数字,向后扫描直到数字串的结尾 | ||
0 | 10 | 扫描到为数字串结尾,数字串压入逆波兰表达式栈 | |
+ | + | 10 | 操作符栈为空,运算符直接压入操作符栈 |
( | +,( | 10 | 扫描到左括号’(’,直接压入操作符栈 |
( | +,(,( | 10 | 扫描到左括号’(’,直接压入操作符栈 |
2 | +,(,( | 10 | 扫描到为数字,向后扫描直到数字串的结尾 |
0 | +,(,( | 10,20 | 扫描到为数字串结尾,数字串压入逆波兰表达式栈 |
+ | +,(,(,+ | 10,20 | 操作符栈栈顶为左括号’(’,运算符直接压入操作符栈 |
3 | +,(,(,+ | 10,20 | 扫描到为数字,向后扫描直到数字串的结尾 |
0 | +,(,(,+ | 10,20,30 | 扫描到为数字串结尾,数字串压入逆波兰表达式栈 |
) | +,( | 10,20,30,+ | 扫描到右括号’)’,弹出操作符栈栈顶运算符并压入逆波兰表达式栈直至遇到左括号,左括号出栈 |
* | +,(,* | 10,20,30,+ | 操作符栈栈顶为左括号’(’,运算符直接压入操作符栈 |
4 | +,(,* | 10,20,30,+ | 扫描到为数字,向后扫描直到数字串的结尾 |
0 | +,(,* | 10,20,30,+,40 | 扫描到为数字串结尾,数字串压入逆波兰表达式栈 |
) | + | 10,20,30,+,40,* | 扫描到右括号’)’,弹出操作符栈栈顶运算符并压入逆波兰表达式栈直至遇到左括号,左括号出栈 |
- | - | 10,20,30,+,40,*,+ | 扫描到的’-‘运算符与运算符栈的栈顶运算符’+‘优先级相同,故将栈顶运算符出栈并压入逆波兰表达式栈中,因为此时操作符栈已为空,故直接将扫描到的’-'压入操作符栈 |
5 | - | 10,20,30,+,40,*,+ | 扫描到为数字,向后扫描直到数字串的结尾 |
0 | - | 10,20,30,+,40,*,+,50 | 扫描到为数字串结尾,数字串压入逆波兰表达式栈 |
中缀表达式扫描完毕 | 10,20,30,+,40,*,+,50,- | 将操作符栈中的运算符弹出并压入逆波兰表达式栈 |
后缀表达式的计算分析过程:
扫描到的单元 | 操作数栈(栈底->栈顶) | 说明 |
---|---|---|
10 | 10 | 扫描到的单元是数字串,则将其转换为数字,并压入操作数栈 |
20 | 10,20 | 扫描到的单元是数字串,则将其转换为数字,并压入操作数栈 |
30 | 10,20,30 | 扫描到的单元是数字串,则将其转换为数字,并压入操作数栈 |
+ | 10,50 | 扫描到的单元是运算符,则对操作数栈栈顶上的两个操作数执行该运算(取两次栈顶)20+30,并 将运算结果50重新压入操作数栈。 |
40 | 10,50,40 | 扫描到的单元是数字串,则将其转换为数字,并压入操作数栈 |
* | 10,2000 | 扫描到的单元是运算符,则对操作数栈栈顶上的两个操作数执行该运算(取两次栈顶)50*40,并 将运算结果2000重新压入操作数栈。 |
+ | 2010 | 扫描到的单元是运算符,则对操作数栈栈顶上的两个操作数执行该运算(取两次栈顶)10+2000,并 将运算结果2010重新压入操作数栈。 |
50 | 2010,50 | 扫描到的单元是数字串,则将其转换为数字,并压入操作数栈 |
- | 1960 | 扫描到的单元是运算符,则对操作数栈栈顶上的两个操作数执行该运算(取两次栈顶)2010-50,并 将运算结果1960重新压入操作数栈。 |
后缀表达式扫描完毕 | 1960 | 后缀表达式单元扫描完毕,最终操作数栈中栈顶元素即为计算结果值 |
代码运行截图:
Java源代码:
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
/**
* 逆波兰式计算器
*
* @author 独孤猿1998
*
*/
public class InversePolishCalculator extends JFrame {
private static final long serialVersionUID = 1L;
private JPanel backgroundPanel;
private JLabel nifixExpressionLabel;
private JTextField nifixExpressionTextField;
private JButton calculateButton;
private JButton clearButton;
private JLabel postfixExpressionLabel;
private JTextField postfixExpressionTextField;
private JLabel calculateResultLabel;
private JTextField calculateResultTextField;
private static Font textFont = new Font("微软雅黑", Font.PLAIN, 18);
private static Font buttonFont = new Font("微软雅黑", Font.PLAIN, 16);
public InversePolishCalculator() {
initialization();
}
/**
* 窗口初始化的方法
*/
public void initialization() {
this.setTitle("逆波兰计算器");//设置窗口标题
this.setSize(550, 320);//设置窗口大小
this.setLocationRelativeTo(null);//设置窗口居中
this.setResizable(false);//设置窗口大小不可改变
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
addViewElement();//窗口添加组件
this.setVisible(true);
}
/**
* 窗口添加组件的方法
*/
public void addViewElement() {
backgroundPanel = new JPanel(null);
nifixExpressionLabel = new JLabel("请输入中缀表达式:");
nifixExpressionLabel.setSize(300, 20);
nifixExpressionLabel.setLocation(40, 50);
nifixExpressionLabel.setFont(textFont);
backgroundPanel.add(nifixExpressionLabel);
nifixExpressionTextField = new JTextField();
nifixExpressionTextField.setSize(300, 30);
nifixExpressionTextField.setLocation(40, 75);
nifixExpressionTextField.setFont(textFont);
backgroundPanel.add(nifixExpressionTextField);
calculateButton = new JButton("计算");
calculateButton.setSize(70, 30);
calculateButton.setLocation(350, 75);
calculateButton.setFont(buttonFont);
calculateButton.setBorder(BorderFactory.createRaisedBevelBorder());
calculateButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
postfixExpressionTextField.setText(nifixTransformPostfix(nifixExpressionTextField.getText()));
calculateResultTextField.setText("" + calculatePostfixExpression(postfixExpressionTextField.getText()));
}
});
backgroundPanel.add(calculateButton);
clearButton = new JButton("清空");
clearButton.setSize(70, 30);
clearButton.setLocation(430, 75);
clearButton.setFont(buttonFont);
clearButton.setBorder(BorderFactory.createRaisedBevelBorder());
clearButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
nifixExpressionTextField.setText("");
postfixExpressionTextField.setText("");
calculateResultTextField.setText("");
}
});
backgroundPanel.add(clearButton);
postfixExpressionLabel = new JLabel("中缀表达式转换后的后缀表达式:");
postfixExpressionLabel.setSize(300, 20);
postfixExpressionLabel.setLocation(40, 115);
postfixExpressionLabel.setFont(textFont);
backgroundPanel.add(postfixExpressionLabel);
postfixExpressionTextField = new JTextField();
postfixExpressionTextField.setSize(300, 30);
postfixExpressionTextField.setLocation(40, 140);
postfixExpressionTextField.setFont(textFont);
backgroundPanel.add(postfixExpressionTextField);
calculateResultLabel = new JLabel("计算结果:");
calculateResultLabel.setSize(300, 20);
calculateResultLabel.setLocation(40, 180);
calculateResultLabel.setFont(textFont);
backgroundPanel.add(calculateResultLabel);
calculateResultTextField = new JTextField();
calculateResultTextField.setSize(300, 30);
calculateResultTextField.setLocation(40, 205);
calculateResultTextField.setFont(textFont);
backgroundPanel.add(calculateResultTextField);
this.setContentPane(backgroundPanel);
}
/**
* 中缀表达式转换为后缀表达式的方法
* @param nifixExpression 中缀表达式
* @return 后缀表达式(逆波兰表达式)
*/
public static String nifixTransformPostfix(String nifixExpression) {
Stack<Character> operatorStack = new Stack<Character>();//初始化存储操作符的操作符栈
Stack<String> postfixExpressionStack = new Stack<String>();//初始化存储逆波兰表达式的逆波兰表达式栈
int length = nifixExpression.length();//中缀表达式的字符串长度
int index = 0;//用于指向扫描到中缀表达式的位置
char element = ' ';//保存当前扫描到中缀表达式的字符
String number = null;//用于存储多位数字
while(index < length) {//从左至右扫描中缀表达式
number = "";
element = nifixExpression.charAt(index);
if (element >= '0' && element <= '9') {//若读取到的字符为数字
while(element >= '0' && element <= '9') {//则向后分析直到数字串的结束
number = number + element;
index++;
if (index == length) {
break;
}
element = nifixExpression.charAt(index);
}
postfixExpressionStack.push(number);//并将数字串压入逆波兰表达式栈
continue;
}else if (element == '(') {//若读取到的字符为'('
operatorStack.push(element);//则直接将'('压入操作符栈
}else if (element == ')') {//若读取到的字符为')'
while (operatorStack.peek() != '(') {//则将操作符栈中的栈顶操作符依次出栈并压入逆波兰表达式栈,直到遇到左括号'('为止
postfixExpressionStack.push(operatorStack.pop().toString());
}
operatorStack.pop();//将操作符栈中栈顶的左括号'('出栈
}else if (element == '+' || element == '-' || element == '*' || element == '/') {//若读取到的字符为运算符'+', '-', '*', '/'
if (operatorStack.isEmpty() || operatorStack.peek() == '(') {//如果操作符栈为空或栈顶元素为'('
operatorStack.push(element);//则将读取到运算符直接压入操作符栈
}else if (Operator.getOperatorPriority(element) > Operator.getOperatorPriority(operatorStack.peek())) {//如果读取的运算符的优先级高于操作符栈栈顶运算符的优先级
operatorStack.push(element);//则将读取的运算符直接压入操作符栈
}else {//如果读取的运算符的优先级低于或等于操作符栈栈顶运算符的优先级
//则将操作符栈栈顶运算符出栈并压入逆波兰表达式栈(注意,此处优先级的比较是不断比较操作符栈栈顶运算符的优先级直到读取到的运算符的优先级高于操作符栈栈顶运算符的优先级或遇到'('或栈空)
while(!operatorStack.isEmpty() && operatorStack.peek() != '(' && Operator.getOperatorPriority(element) <= Operator.getOperatorPriority(operatorStack.peek())) {
postfixExpressionStack.push(operatorStack.pop().toString());
}
operatorStack.push(element);//将读取到的运算符压入操作符栈。
}
}else {
throw new RuntimeException("扫描到未知字符!");
}
index++;
}
while(!operatorStack.isEmpty()) {//中缀表达式扫描完毕,若操作符栈中仍然存在运算符
postfixExpressionStack.push(operatorStack.pop().toString());//则将操作符栈中栈顶的运算符依次出栈并压入逆波兰表达式栈直到操作符栈为空
}
//逆波兰表达式栈的出栈元素的逆序为中缀表达式转换后的后缀表达式(用空格隔开)
String postfixExpression = postfixExpressionStack.pop();
while(!postfixExpressionStack.isEmpty()) {
postfixExpression = postfixExpressionStack.pop() + " " + postfixExpression;
}
return postfixExpression;
}
/**
* 计算后缀表达式结果的方法
* @param postfixExpression 后缀表达式(逆波兰表达式)
* @return 后缀表达式的计算结果
*/
public static int calculatePostfixExpression(String postfixExpression) {
Stack<Integer> operandStack = new Stack<Integer>();//初始化一个操作数栈
String[] elements = postfixExpression.split(" ");//将后缀表达式分解成一个个单元
String element = null;//后缀表达式扫描到的单元
char elementHead = ' ';
int operand = 0, operand1 = 0, operand2 = 0;
for (int i = 0; i < elements.length; i++) {//从左至右依次扫描后缀表达式的单元
element = elements[i];
elementHead = element.charAt(0);
if (elementHead >= '0' && elementHead <= '9') {//如果扫描的单元是操作数串
operand = Integer.parseInt(element);//则将其转换为数字
operandStack.push(operand);//并压入操作数栈
}else if(elementHead == '+' || elementHead == '-' || elementHead == '*' || elementHead == '/') {// 如果扫描的单元是一个运算符
operand1 = operandStack.pop();//取操作数栈栈顶
operand2 = operandStack.pop();//取操作数栈栈顶
operand = calculateResult(operand1, operand2, elementHead);//则对操作数栈栈顶上的两个操作数执行该运算
operandStack.push(operand);//将运算结果重新压入操作数栈
}else {
throw new RuntimeException("扫描到未知单元!");
}
}
return operandStack.pop();
}
/**
* 用于计算给定操作数及操作符的结果的方法
* @param operand1
* @param operand2
* @param operator
* @return 计算结果
*/
public static int calculateResult(int operand1, int operand2, char operator) {
int result = 0;
switch (operator) {
case '+':
result = operand2 + operand1;
break;
case '-':
result = operand2 - operand1;
break;
case '*':
result = operand2 * operand1;
break;
case '/':
result = operand2 / operand1;
break;
default:
throw new RuntimeException("未定义运算符:" + operator);
}
return result;
}
public static void main(String[] args) {
new InversePolishCalculator();
}
}
/**
* 运算符类,用于比较运算符的优先级
*/
class Operator {
private static final int ADD = 1;//'+' 的优先级定义
private static final int SUB = 1;//'-' 的优先级定义
private static final int MUL = 2;//'*' 的优先级定义
private static final int DIV = 2;//'/' 的优先级定义
/**
* 获取给定运算符的优先级的犯法
* @param operator 给定的运算符
* @return 给定运算符的优先级
*/
public static int getOperatorPriority(char operator) {
int priority = 0;
switch (operator) {
case '+':
priority = ADD;
break;
case '-':
priority = SUB;
break;
case '*':
priority = MUL;
break;
case '/':
priority = DIV;
break;
default:
throw new RuntimeException("未定义运算符:" + operator);
}
return priority;
}
}
/**
* 使用数组实现栈类
*/
class Stack<E> {
private int maxSize;//栈的最大容量
private Object[] elements;
private int top;//指向栈顶元素
public Stack() {
this(30);
}
public Stack(int maxSize) {
this.maxSize = maxSize;
elements = new Object[this.maxSize];
top = -1;
}
/**
* 判断栈是否为空的方法
* @return 判断是否栈空的结果
*/
public boolean isEmpty() {
return top == -1;
}
/**
* 判断栈是否已满的方法
* @return 判断是否栈满的结果
*/
public boolean isFull() {
return top == maxSize - 1;
}
/**
* 元素入栈的方法
* @param element 即将入栈的元素
*/
public void push(E element) {
if (isFull()) {
System.out.println("栈满,元素无法入栈!");
return ;
}
elements[++top] = element;
}
/**
* 取栈顶元素的方法,栈顶元素不出栈
* @return 栈顶元素
*/
public E peek() {
if (isEmpty()) {
throw new RuntimeException("栈空,无法取栈顶元素!");
}
return (E)elements[top];
}
/**
* 栈顶元素出栈的方法
* @return 栈顶出栈的元素
*/
public E pop() {
if (isEmpty()) {
throw new RuntimeException("栈空,栈顶元素无法出栈!");
}
return (E)elements[top--];
}
}
运行环境:
jdk: java version “1.8.0_241”
eclipse:
Eclipse IDE for Java Developers
Version: 2019-12 (4.14.0)