五, Java实现栈以及栈的一些应用 0.5

五, 栈

5.1 栈的特点和应用

  • 栈(Stack), 是一个先入后出(FIFO)的有序列表;
  • 栈是一种限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表; 允许插入和删除的一端,为变化的一端,成为 栈顶(Top), 另一端为固定的一端,成为栈底(Bottom);
  • 根据栈的定义可知, 最先放入栈中的元素处于栈底, 最后放入的元素在栈顶, 而取出元素刚好相反,最后放入的元素最先删除, 最先放入的元素最后删除;

栈的几点应用

5.2 数组模拟实现栈

实现思路

1.使用数组来模拟栈
2. 定义一个top来表示栈顶, 初始化为-1(其余时间, top都是指向栈顶第一个数据)
3. 入栈操作,当有数据加入到栈时,top++; arr[top]=data;
4. 出栈操作, 当有数据要出栈时, int val = arr[top]; top–; return val;

注意:

跟前面数组模拟实现队列类似, 我们在插入数组的时候需要判满, 从数组中取出数据的时候需要判空! 切记!

完整代码实例-数组模拟实现栈的出入栈和打印输出(Java实现)

5.3 栈实现计算器(利用栈计算中缀表达式的值)

实现思路:

  1. 工具: 一个表达式及遍历表达式字符串的索引, 一个存储数字的栈numStack, 一个存储运算符号的栈opStack;
  2. 通过一个index值(索引), 来遍历我们的表达式;
  3. 如果我们发现是一个数字,那么直接把他放入数栈;
  4. 如果我们发现扫描到一个符号,就分如下情况:

3.1 如果发现当前的符号栈为空,那么直接把运算符号入栈即可;
3.2 如果符号栈有操作符,就进行比较, 如果这个要入栈的操作符优先级小于或等于栈顶的操作符,则我们先拿两个栈中的(注意是栈中的哦! )元素进行运算 (从数栈pop两个数, 从运算符栈pop出栈顶的这个操作符, 然后进行运算), 得到的结果存入到数栈,并把之前比较的那个优先级小一些的符号入符号栈; 如果当前的操作符的优先级大于栈顶的,就直接放入符号栈;

  1. 当表达式扫描完毕,就顺序的从数栈和符号栈pop出相应的数和符号,进行运算;
  2. 最后在数栈中只有一个数字,就是表达式的结果,pop出来返回即可.

5.4 中缀, 前缀和后缀表达式

5.4.1 前缀表达式的计算机求值

完整代码实例-Java实现基于前缀表达式的计算器

5.4.2 后缀表达式的计算机求值

中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作(前面我们讲的案例就能看的这个问题),因此,在计算结果时,往往会将中缀表达式转成其它表达式来操作(一般转成后缀表达式.)

5.4.3 逆波兰计算器(给定了后缀表达式)

思路: 5.4.2节

具体实现步骤:

  1. 把后缀表达式通过split(" ")方法根据空格分割开, 然后放在String数组中;
  2. 遍历数组, 把表达式的单个数或操作符一一存放在集合中;
  3. 新建一个存储数字的栈, 遍历集合, 把其中的数字(通过正则表达式判断数字)放入到栈中;
  4. 当我们遍历到操作符号时,马上从数字栈中出栈两次,把计算后的结果仍旧存放到栈中;
  5. 当遍历完毕后, 栈中的唯一一个数字就是表达式的结果, 出栈即可.

代码实现:

package DataStrcture.StackDemo;

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

public class PolanExp {
    ///存储表达式到list中去
    public static void main(String[] args) {
        /*
        待测试表达式: (30+4)x5-6

         */
        String suffixExp = "30 4 + 5 x 6 -";

        PolanExp pe = new PolanExp();
        List<String> list = pe.getString(suffixExp);// 得到表达式list

        System.out.println(list);
        int res = pe.calculate(list);
        System.out.println("运算结果为: "+res);
    }

    /// 把表达式字符串分隔后存入到list中
    public List<String> getString(String suffixExp) {
        List<String> list = new ArrayList<String>();

        String[] str = suffixExp.split(" ");
        //存储到arrryList
        for (String item : str)
            list.add(item);

        return list;
    }

    ///计算方法
    public int calculate(List<String> ls) {
        ///遍历集合,取出其中的数放到栈中
        //新建一个数字栈,专门存储数字
        Stack<String> stack = new Stack<String>();
        int res = 0;
        for (String item : ls) {
            ///如何判断是个数字
            //正则表达式,匹配数字
            if (item.matches("\\d+")) {
                stack.push(item);
            } else {

                //数字全部放到栈里面了, 我们拿出来俩开始运算
                //在这里,我们需要把 String 转为int
                /*
                String 转为int的两种方式
                1.间接法: String-->Integer-->int
                     int num = Integer.parseInt(str);
                2.直接法: String--> int
                     Integer in = Integer.valueOf(str);
                     int num = in.intValue();
                 */

                int num_2 = Integer.parseInt(stack.pop());
                int num_1 = Integer.parseInt(stack.pop());

                //取操作符

                if (item.equals("+")) {
                    res = num_1 + num_2;
                } else if (item.equals("-")) {
                    res = num_1 - num_2;
                } else if (item.equals("x")) {
                    res = num_1 * num_2;
                } else if (item.equals("/")) {
                    res = num_1 / num_2;
                } else {
                    throw new RuntimeException("操作符输入有误");
                }
                String string_res = "" + res;
                stack.push(string_res);
            }
        }

        int real_res = Integer.parseInt(stack.pop());
        return real_res;
    }
}


5.4.3 中缀表达式转换为后缀表达式

关于后缀表达式转中缀表达式的详细栗子和说明

具体实现过程:

  1. 首先我们需要一个集合suffixList存储最终的后缀表达式结果, 还需要一个栈存放操作符和括号;
  2. 遍历传入的表达式集合inffixList,
  3. 如果取出的元素是数字,直接放入后缀表达式集合;
  4. 如果取出的元素是"(",左括号, 压入栈中;
  5. 如果取出的元素是"**)**"右括号,则将栈元素弹出, 直到遇到了左括号"(",弹出的元素全部放入suffixList集合中; 注意, 括号直接舍弃!
  6. 如果取出的元素是正儿八经的加减乘除操作符:,那么我们将会根据优先级来判断入栈的时机:

6.1 从inffixList集合中取出的操作符的优先级小于栈顶(stack.peek())的,并且栈不为空,并且栈顶的元素不是"("就出栈并把出栈元素添加到集合中
6.2 相反的情况(操作符优先级大于栈顶元素)直接入栈即可,甚至不用写判断条件

  1. 表达式遍历完了之后,栈中还有元素就直接全部出栈并添加到集合中即可!
  2. 全部工作完成, 返回suffixList即可;

中缀转后缀的主要方法实现代码如下:

 ///中缀表达式转后缀表达式
    public List<String> transferToSuffixExp(List<String> list) {
        
        List<String> suffixExpList = new ArrayList<String>();// 存储后缀表达式的元素
        Stack<String> opStack = new Stack<String>();//存储符号栈

        //遍历集合
        for (String item : list) {
            if (item.matches("\\d+")) {//1. 数字的直接存到集合
                suffixExpList.add(item);
            } else if (item.equals("(")) {//2. 左括号直接压到栈中
                opStack.push(item);
            } else if (item.equals(")")) {
                //3. 遇见右括号,嘛也不管直接不停的出栈直到遇到了左括号
                while (!(opStack.peek().equals("("))) {
                    suffixExpList.add(opStack.pop());
                }
                opStack.pop();
                // 4.遇到的都是正儿八经的操作符,比较优先级后再决定入栈时机
            } else {
//                // 待插入栈的优先级大于栈顶操作符的优先级()
//                if (opStack.size() == 0 || (opStack.peek().equals("(") || getPrioity(item) > getPrioity(opStack.peek()))) {
//                    opStack.push(item);
//                } else {
                    ///待插入栈的优先级小于或者等于栈顶元素, 不停的出栈 (切记别忘了判空,更不要忘了忽略栈顶是括号的情况(是括号就直接入栈))
                    while (opStack.size() > 0 && getPrioity(opStack.peek()) >= getPrioity(item) && !(opStack.peek().equals("(") ))
                        suffixExpList.add(opStack.pop());
                    opStack.push(item);
//                }
            }
        }
        ///集合遍历完了,栈中还剩下操作符,直接全部pop出放入后缀表达式的集合就行啦
        while (opStack.size() > 0)
            suffixExpList.add(opStack.pop());
        return suffixExpList;
    }
}

5.4.4 优化后的逆波兰计算器

此处就是对前两节的一个整合

完整代码实例-Java实现简单的逆波兰计算器

posted @ 2022-05-26 20:31  青松城  阅读(62)  评论(0编辑  收藏  举报