数据结构与算法二:2)桟的应用--逆波兰表达式

1,123 阅读10分钟

这是我参与8月更文挑战的第14天,活动详情查看:8月更文挑战

关注我,以下内容持续更新

数据结构与算法(一):时间复杂度和空间复杂度

数据结构与算法(二):桟

数据结构与算法(三):队列

数据结构与算法(四):单链表

数据结构与算法(五):双向链表

数据结构与算法(六):哈希表

数据结构与算法(七):树

数据结构与算法(八):排序算法

数据结构与算法(九):经典算法面试题

一 预览

中缀表达式前缀表达式后缀表达式
2*3/(2-1)+3*(4-1)+/*23-21*3-4123*21-/341-*+

上一篇我们介绍了数据结构与算法二:1)桟 这篇主要介绍栈的应用--逆波兰表达式,逆波兰表达式就是后缀表达式

中缀表达式对我们而言是很直观的(我们平时接触的就是这个),但计算机处理起来比较麻烦(括号、优先级之类的),前缀和后缀表达式中没有括号(因为中缀表达式转为前/后缀表达式后括号被舍弃,依旧保持正确的计算顺序),而且在计算中只需单向扫描(前缀表达式从右向左扫描,后缀表达式从左向右扫描),不需要考虑运算符的优先级。

二 后缀表达式

后缀表达式也叫逆波兰表达式,后缀表达式的运算符位于操作数之后.

举例说明:(3+4)x5-6对应的后缀表达式就是 3 4 + 5 x 6 - ; 至于后缀表达式怎么来的,后边会讲如何将中缀表达式转为后缀表达式

再比如:

正常的表达式逆波兰表达式
a+bab+
a+(b-c)abc-+
a+(b-c)*dabc-d*+
a+d*(b-c)adbc-*+
a=1+3a13+=
a+bab+

1. 后缀表达式求值:

1) 计算思路

从左至右取数,直到取出一个运算符,将刚取出的紧挨着运算符的两个操作数按运算符进行计算,结果回填至运算符。重复该步骤,直到最后只剩下一个字符串则剩下的字符串即为结果。

前缀表达式的计算规则和后缀表达式相似,唯一不同的是扫描方向相反. 前序表达式是从右往左,后序表达式是从左往右.

2) 代码思路

① 计算规则

从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 和 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果

② 计算过程

以后缀表达式'3 4 + 5 x 6 -'为例, 求值步骤如下:

  1. 从左至右扫描,将3和4压入堆栈;
  2. 遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
  3. 将5入栈;
  4. 接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
  5. 将6入栈;
  6. 最后是-运算符,计算出35-6的值,即29,由此得出最终结果

后缀表达式求值代码(先把字符串转为数组)

/**
1)从左至右扫描,将3和4压入堆栈;
2)遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
3)将5入栈;
4)接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
5)将6入栈;
6)最后是-运算符,计算出35-6的值,即29,由此得出最终结果
*/
public static int calculate(List<String> ls) {
    // 创建个栈, 只需要一个栈即可
    Stack<String> stack = new Stack<String>();

    // 遍历 ls
    for (String item : ls) {

        // 这里使用正则表达式来取出数
        if (item.matches("\\d+")) { // 匹配的是多位数
            stack.push(item);// 入栈
        } else {

            // pop出两个数,并运算, 再入栈

            int num2 = Integer.parseInt(stack.pop());

            int num1 = Integer.parseInt(stack.pop());

            int res = 0;

            if (item.equals("+")) {

                res = num1 + num2;

            } else if (item.equals("-")) {

                res = num1 - num2;

            } else if (item.equals("*")) {

                res = num1 * num2;

            } else if (item.equals("/")) {

                res = num1 / num2;

            } else {
                throw new RuntimeException("运算符有误");
            }

            //把res 入栈
            stack.push("" + res);
        }

    }

    //最后留在stack中的数据是运算结果
    return Integer.parseInt(stack.pop());
}

2. 中缀表达式转后缀表达式

步骤

中缀表达式转后缀表达式步骤
1)初始化两个栈:运算符栈s1和储存中间结果的栈s2;
2)从左至右扫描中缀表达式;
3)遇到操作数时,将其压s2;
4)遇到运算符时,比较其与s1栈顶运算符的优先级:
  4.1)如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
  4.2)否则,若优先级比栈顶运算符的高,也将运算符压入s1;
  4.3)否则(优先级小于/等于栈顶运算符),将s1栈顶的运算符弹出并压入到s2中,再次转到(4.1)与s1中新的栈顶运算符相比较
5)遇到括号时:
  5.1) 如果是左括号“(”,则直接压入s1
  5.2) 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
6)重复步骤25,直到表达式的最右边
7)将s1中剩余的运算符依次弹出并压入s2
8)依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式

举例说明:将中缀表达式“1+((2+3)×4)-5”转换为后缀表达式的过程如下,因此结果为 "1 2 3 + 4 × + 5 –"

中缀转后缀.png

代码如下:

//将中缀表达式对应的List转为后缀表达式对应的List
//即 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]  =》 ArrayList [1,2,3,+,4,*,+,5,–]

public static List<String> parseSuffixExpreesionList(List<String> ls){
    //定义两个栈:符号栈和存储中间结果的桟
    Stack<String> s1 = new Stack<String>(); // 符号栈

    //说明:因为s2 这个栈,在整个转换过程中,没有pop操作,而且后面我们还需要逆序输出
    //因此比较麻烦,这里我们就不用 Stack<String> 直接使用 List<String> s2
    //Stack<String> s2 = new Stack<String>(); // 储存中间结果的栈s2

    List<String> s2 = new ArrayList<String>(); // 储存中间结果的Lists2

    //遍历ls
    for(String item: ls) {
        if(item.matches("\\d+")) {//匹配多位数
            s2.add(item);// //如果是一个数,直接加入s2
        } else if (item.equals("(")) {
            s1.push(item);//如果是(,直接加入s1
        } else if (item.equals(")")) {
            //如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
            while(!s1.peek().equals("(")) {
                s2.add(s1.pop());
            }
            s1.pop();//!!! 将 ( 弹出 s1栈, 消除小括号
        } else {
            //当item的优先级小于等于s1栈顶运算符, 将s1栈顶的运算符弹出并加入到s2中,再次转到(4.1)与s1中新的栈顶运算符相比较
            //注意:这里有一个比较优先级高低的方法Operation.getValue()
            while(s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item) ) {
                s2.add(s1.pop());
            }
            //还需要将item压入栈
            s1.push(item);
        }
    }
    //将s1中剩余的运算符依次弹出并加入s2
    while(s1.size() != 0) {
        s2.add(s1.pop());
    }
    return s2; //注意因为是存放到List, 因此按顺序输出就是对应的后缀表达式对应的List   
}

3. 后缀表达式的应用-逆波兰计算器完整代码

例如计算 1+((2+3)×4)-5

/**
 实现一个逆波兰计算器 
 思路: 例如计算 1+((2+3)×4)-5
 第一步:先将一个中缀表达式字符串转为数组(因为字符串不方便运算)
 第二步:将中缀表达式数组转成后缀表达式数组:
 1+((2+3)×4)-5 => 转成  1 2 3 + 4 × + 5 –
 第三步:对后缀表达式求值
 */
public static void main(String[] args) {
    String expression = "1+((2+3)*4)-5";//注意表达式
    //第一步:先将中缀表达式的字符串转换为数组
    //因为直接对str进行操作不方便,因此先将字符串转为数组
    List<String> infixExpressionList = toInfixExpressionList(expression);
    
    System.out.println("中缀表达式对应的List=" + infixExpressionList); // ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]
    
    //第二步:中缀表达式的数组转为后缀表达式的数组
    List<String> suffixExpreesionList = parseSuffixExpreesionList(infixExpressionList);

    System.out.println("后缀表达式对应的List" + suffixExpreesionList); //ArrayList [1,2,3,+,4,*,+,5,–]

    //第三步:用后缀表达式求值
    System.out.printf("expression=%d", calculate(suffixExpreesionList));
}
//第一步:将中缀表达式字符串转成对应的数组
//  s="1+((2+3)×4)-5";
public static List<String> toInfixExpressionList(String s) {

    //定义一个List,存放中缀表达式 对应的内容
    List<String> ls = new ArrayList<String>();

    int i = 0; //这时是一个指针,用于遍历 中缀表达式字符串

    String str; // 对多位数的拼接

    char c; // 每遍历到一个字符,就放入到c

    do {

        //如果c是一个非数字,我需要加入到ls

        if((c=s.charAt(i)) < 48 ||  (c=s.charAt(i)) > 57) {

            ls.add("" + c);

            i++; //i需要后移

        } else { //如果是一个数,需要考虑多位数

            str = ""; //先将str 置成"" '0'[48]->'9'[57]

            while(i < s.length() && (c=s.charAt(i)) >= 48 && (c=s.charAt(i)) <= 57) {

                str += c;//拼接

                i++;

            }

            ls.add(str);

        }

    }while(i < s.length());

    return ls;//返回
}
//第二步:将中缀表达式对应的List转为后缀表达式对应的List
//即 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]  =》 ArrayList [1,2,3,+,4,*,+,5,–]

public static List<String> parseSuffixExpreesionList(List<String> ls){
    //定义两个栈:符号栈和存储中间结果的桟
    Stack<String> s1 = new Stack<String>(); // 符号栈

    //说明:因为s2 这个栈,在整个转换过程中,没有pop操作,而且后面我们还需要逆序输出
    //因此比较麻烦,这里我们就不用 Stack<String> 直接使用 List<String> s2
    //Stack<String> s2 = new Stack<String>(); // 储存中间结果的栈s2

    List<String> s2 = new ArrayList<String>(); // 储存中间结果的Lists2

    //遍历ls
    for(String item: ls) {
        if(item.matches("\\d+")) {//匹配多位数
            s2.add(item);// //如果是一个数,直接加入s2
        } else if (item.equals("(")) {
            s1.push(item);//如果是(,直接加入s1
        } else if (item.equals(")")) {
            //如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
            while(!s1.peek().equals("(")) {
                s2.add(s1.pop());
            }
            s1.pop();//!!! 将 ( 弹出 s1栈, 消除小括号
        } else {
            //当item的优先级小于等于s1栈顶运算符, 将s1栈顶的运算符弹出并加入到s2中,再次转到(4.1)与s1中新的栈顶运算符相比较
            //注意:这里有一个比较优先级高低的方法Operation.getValue()
            while(s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item) ) {
                s2.add(s1.pop());
            }
            //还需要将item压入栈
            s1.push(item);
        }
    }
    //将s1中剩余的运算符依次弹出并加入s2
    while(s1.size() != 0) {
        s2.add(s1.pop());
    }
    return s2; //注意因为是存放到List, 因此按顺序输出就是对应的后缀表达式对应的List   
}
//第三步:逆波兰表达式的求值
/**
1)从左至右扫描,将3和4压入堆栈;
2)遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
3)将5入栈;
4)接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
5)将6入栈;
6)最后是-运算符,计算出35-6的值,即29,由此得出最终结果
*/
public static int calculate(List<String> ls) {

    // 创建个栈, 只需要一个栈即可
    Stack<String> stack = new Stack<String>();

    // 遍历 ls
    for (String item : ls) {

        // 这里使用正则表达式来取出数
        if (item.matches("\\d+")) { // 匹配的是多位数
            stack.push(item);// 入栈
        } else {

            // pop出两个数,并运算, 再入栈

            int num2 = Integer.parseInt(stack.pop());

            int num1 = Integer.parseInt(stack.pop());

            int res = 0;

            if (item.equals("+")) {

                res = num1 + num2;

            } else if (item.equals("-")) {

                res = num1 - num2;

            } else if (item.equals("*")) {

                res = num1 * num2;

            } else if (item.equals("/")) {

                res = num1 / num2;

            } else {
                throw new RuntimeException("运算符有误");
            }

            //把res 入栈
            stack.push("" + res);
        }

    }

    //最后留在stack中的数据是运算结果
    return Integer.parseInt(stack.pop());
}
//编写一个类 Operation 可以返回一个运算符 对应的优先级

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 result = 0;

        switch (operation) {

        case "+":

            result = ADD;

            break;

        case "-":

            result = SUB;

            break;

        case "*":

            result = MUL;

            break;

        case "/":

            result = DIV;

            break;

        default:

            System.out.println("不存在该运算符" + operation);

            break;

        }
        return result;
    }
}

三 关注我

如果觉得我写的不错,请点个赞 关注我 您的支持是我更文最大的动力!