栈java实现(三)逆波兰(后缀)表达式及简单后缀计算器

栈的应用——逆波兰表达式

  • 一、逆波兰(后缀)表达式
  • 二、中缀表达式→后缀表达式
    • 1、思路分析
    • 2、代码实现
  • 三、简单逆波兰表达式计算器
    • 1、思路分析
    • 2、代码实现

一、逆波兰(后缀)表达式

逆波兰式(Reverse Polish notation,RPN,或逆波兰记法),也叫后缀表达式(将运算符写在操作数之后)

定义

一个表达式E的后缀形式可以如下定义:
(1)如果E是一个变量或常量,则E的后缀式是E本身。
(2)如果E是E1 op E2形式的表达式,这里op是任何二元操作符,则E的后缀式为E1’E2’ op,这里E1’和E2’分别为E1和E2的后缀式。
(3)如果E是(E1)形式的表达式,则E1的后缀式就是E的后缀式。
如:我们平时写a+b,这是中缀表达式,写成后缀表达式就是:ab+
(a+b)c-(a+b)/e的后缀表达式为:
(a+b)c-(a+b)/e
→((a+b)c)((a+b)/e)-
→((a+b)c
)((a+b)e/)-
→(ab+c
)(ab+e/)-
→ab+c
ab+e/-

作用

实现逆波兰式的算法,难度并不大,但为什么要将看似简单的中缀表达式转换为复杂的逆波兰式?原因就在于这个简单是相对人类的思维结构来说的,对计算机而言中序表达式是非常复杂的结构。相对的,逆波兰式在计算机看来却是比较简单易懂的结构。因为计算机普遍采用的内存结构是栈式结构,它执行先进后出的顺序。

二、中缀表达式→后缀表达式

1、思路分析

  1. 初始化两个栈:运算符栈s1,存储中间结果的栈s2
  2. 从左至右扫描中缀表达式
  3. 扫描到的是操作数,压入s2
  4. 扫描到的是运算符:
    1. 如果s1为空,或栈顶运算符为“)”,则直接将此运算符压入s1
    2. 如果s1不为空,则比较其与s1栈顶运算符的优先级
      1. 若该运算符优先级比栈顶运算符的高,则直接将此运算符压入s1
      2. 若该运算符优先级比栈顶运算符的低,将s1栈顶的运算符弹出并压入到s2中,再重新回到4.1进行判断。
  5. 扫描到的是括号:
    1. 如果是左括号“(”,则直接压入s1
    2. 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃(pop出栈)
  6. 扫描直到表达式最右端
  7. 将s1中剩余的运算符依次弹出并压入s2
  8. 依次弹出s2中的元素并输出,结果的逆序即为所要的后缀表达式(因为栈是先入后出的,所以直接pop后其实是对应后缀表达式的一个逆序,下面的代码实现中我们使用list来代替这里的s2,避免了结果还需要逆序输出)

2、代码实现

1. 首先将中缀表达式中的各个字符按序存放到一个List中方便后面的遍历使用

/**
     * 将中缀表达式中各个字符存储到List中利于后续的遍历使用
    * @param s 中缀表达式
     * @return  存储中缀表达式中各字符的List
     */
    public static List<String> toInfixExpressionList(String s) {
        List<String> list = new ArrayList<>();
        int i = 0;//用于遍历的指针
        String str;//多位数的拼接
        char c;//每遍历到一个字符就放到c
        do {
            //如果c是一个非数字就加入到list
            if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57) {
                list.add("" + c);
                i++;
            } else {//如果是一个数字需要考虑多位数问题
                str = "";
                while (i < s.length() && (c = s.charAt(i)) >= 48 && (c = s.charAt(i)) <= 57) {
                    str += c;//拼接
                    i++;
                }
                list.add(str);
            }
        } while (i < s.length());
        return list;
    }

2. 定义一个类来进行运算符优先级的判断

class Operation {
    private static int ADD = 1;
    private static int SUB = 1;
    private static int MUL = 2;
    private static int DIV = 2;

    public static int getValue(String operation) {
        int res = 0;
        switch (operation) {
            case "+":
                res = ADD;
                break;
            case "-":
                res = SUB;
                break;
            case "*":
                res = MUL;
                break;
            case "/":
                res = DIV;
                break;
        }
        return res;
    }

3. 遍历List中存放的字符,按照分析思路进行转换
注意:由于存放中间结果的栈s2在转换过程中仅仅包含入栈push操作,所以我们这里用一个List来代替存放中间结果的栈s2,这样代替也可以达到目的,并且遍历输出list后就是对应的后缀表达式,无需再进行逆序输出。

/**
     * 中缀表达转换为后缀表达式
     * @param ls 存放中缀表达式各符号的list
     * @return  存放后缀表达式的List
     */
    public static List<String> parseSuffixExpression(List<String> ls) {
        //定义两个栈
        Stack<String> stack = new Stack<>();
        List<String> list = new ArrayList<>();
        //遍历ls
        for (String str : ls) {
            if (str.matches("\\d+")) {//正则表达式:匹配多位数
                list.add(str);
            } else if (str.equals("(")) {
                stack.push(str);
            } else if (str.equals(")")) {
                while (!stack.peek().equals("(")) {
                    list.add(stack.pop());
                }
                stack.pop();//将(弹出,消除小括号
            } else {
                while (stack.size() != 0 && Operation.getValue(stack.peek()) >= Operation.getValue(str)) {//判断优先级
                    list.add(stack.pop());
                }
                stack.push(str);
            }
        }
        while (stack.size() != 0) {
            list.add(stack.pop());
        }
        return list;
    }

4. 写一个Demo进行测试

import java.util.*;
/**
 1. @author dankejun
 2. @create 2020-04-19 13:41
 */
public class PolandNotation {
    public static void main(String[] args) {
//        String expression = "1+((2+3)*4)-5";
        System.out.print("请输入一个中缀表达式:");
        Scanner scanner = new Scanner(System.in);
        String expression = scanner.next();
        List<String> infixExpressionList = toInfixExpressionList(expression);//存放中缀表达式各字符的List
        System.out.println("中缀表达式对应的List" + infixExpressionList);

        List<String> parseSuffixExpression = parseSuffixExpression(infixExpressionList);//存放后缀表达式各字符的List
        System.out.println("后缀表达式对应的List" + parseSuffixExpression);

		//遍历输出对应的后缀表达式
        String suffixExpression = "";
        for (String s : parseSuffixExpression) {
            suffixExpression += s;
        }
        System.out.println("输入的中缀表达式对应的后缀表达式为:" + suffixExpression);
}
}

测试结果:
栈java实现(三)逆波兰(后缀)表达式及简单后缀计算器_第1张图片

三、简单逆波兰表达式计算器

1、思路分析

这里我们对逆波兰表达式进行一个简单的应用,完成一个简单的支持小括号和多位整数的逆波兰表达式计算器。
思路分析:

  1. 初始化一个栈s
  2. 从左至右扫描表达式
  3. 如果扫描到的是操作数,则直接入栈
  4. 如果扫描到的是运算符,就pop出栈顶元素num2与次顶元素num1,并用该运算符进行运算,最后将结果压入栈s中
  5. 直到栈中剩下一个元素,即为最后的计算结果

2、代码实现

1. 首先将逆波兰表达式放到List中,方便遍历使用,为了方便,逆波兰表达式的各字符用空格隔开

 /**
     * 将逆波兰表达式放到List中,方便遍历使用
     * @param suffixExpression 逆波兰表达式
     * @return  存储逆波兰表达式中各字符的List
     */
    public static List<String> getListString(String suffixExpression) {
        String[] split = suffixExpression.split(" ");
        ArrayList<String> list = new ArrayList<>();
        for (String str : split) {
            list.add(str);
        }
        return list;
    }

2. 实现基本的计算功能

 /**
     * 逆波兰计算器
     * @param ls 存储逆波兰表达式中各字符的List
     * @return  逆波兰计算器计算结果
     */
    public static int calculate(List<String> ls) {
        Stack<String> stack = new Stack<>();
        //遍历
        for (String str : ls) {
            if (str.matches("\\d+")) {//正则表达式:匹配多位数
                //入栈
                stack.push(str);
            } else {
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                if (str.equals("+")) {
                    res = num1 + num2;
                } else if (str.equals("-")) {
                    res = num1 - num2;
                } else if (str.equals("*")) {
                    res = num1 * num2;
                } else if (str.equals("/")) {
                    res = num1 / num2;
                } else {
                    throw new RuntimeException("运算符有误");
                }
                stack.push(res + "");
            }
        }
        return Integer.parseInt(stack.pop());
    }

3. 写一个Demo进行简单测试

import java.util.*;

/**
 * @author dankejun
 * @create 2020-04-19 13:41
 */
public class PolandNotation {
    public static void main(String[] args) {
        //先定义一个逆波兰表达式
        //为了方便,逆波兰表达式的各字符用空格隔开
        String suffixExpression = "3 4 + 5 * 6 -";
        
        List<String> rpnList = getListString(suffixExpression);
        System.out.println(rpnList);

        int res = calculate(rpnList);
        System.out.println(res);
    }

测试结果:
栈java实现(三)逆波兰(后缀)表达式及简单后缀计算器_第2张图片
这里只是对逆波兰计算器的一个简单的实现,也可以将中缀表达式转后缀表达式与后缀表达式计算器结合起来,完成一个支持括号和多位整数的中缀表达式计算器。

你可能感兴趣的:(java数据结构)