栈和栈的应用

一、栈

1、栈的介绍

定义:栈是只允许在一端插入或删除的特定线性表。
栈的应用场景:
1、子程序的调用:在跳往子程序前,会将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
2、处理递归调用:和子程序调用类似,除了存储下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
3、 表达式的转换[中缀表达式转后缀表达式]与求值(实际解决)—使用操作数栈和运算符栈来完成
4、二叉树的遍历—行遍历
5、形的深度优先(depth—first)搜索法。

2、使用数组来模拟栈

思路分析:
① 定义一个top来表示栈顶,初始化为-1
② 入栈的操作,当有数据加入到栈时,top++;stack[top]=data;
③ 出战的操作,int value =stack[top]; top–; return value;

定义一个ArrayStack类
代码如下:

// 定义一个ArrayStack表示栈
class ArrayStack{
    private int maxSize; // 栈的大小
    private int[] stack; // 数组模拟栈,数据就放在该数组
    private int top =-1; //top表示栈顶,初始化为-1

    // 构造器
    public ArrayStack(int maxSize) {
        this.maxSize = maxSize;
        stack = new int[this.maxSize];  //初始化数组,
                                    // 并赋值给数组类型的变量stack
    }


    // 栈满
    public boolean isFull(){
        return top==maxSize-1;
    }

    // 栈空
    public boolean isEmpty(){
        return top==-1;
    }

    //入栈 push
    public void stackPush(int value){

        if (top==maxSize-1) {
            System.out.println("栈满---不能再添加");
            return;
        }
        top++;
        stack[top]=value;
    }

    // 出栈 pop,将栈顶的数据返回
    public int stackPop(){
        if (top==-1){
            // 抛出异常
            throw new RuntimeException("栈空,没有数据---");
        }
        int temp=stack[top]; // 记录下要出栈的值,
        top--;                    // 如果直接return,就不能完成top--了
        return temp;
    }

    // 遍历栈 从栈顶开始到栈底
    public void showStack(){
        if (top==-1){
            System.out.println("栈空,没有数据---");
            return;
        }
        for (int i=top;i>=0;i--){
            System.out.printf("stack[%d]=%d\n",i,stack[i]);
        }
    }
}

测试如下:

public class ArrayStackDemo {
    public static void main(String[] args) {
        // 先创建一个ArrayStack对象---表示栈
        ArrayStack arrayStack = new ArrayStack(3);

        String key="";  // 保存后续选择
        boolean loop=true; // 控制是否退出菜单
        Scanner scanner = new Scanner(System.in);//获取一个扫描器

        while (loop){
            System.out.println("show:表示显示栈");
            System.out.println("exit:退出栈");
            System.out.println("push:表示添加数据到栈(入栈)");
            System.out.println("pop:表示从栈取出数据(出栈)");
            System.out.println("请输入你的选择");
            key=scanner.next();
            switch (key){
                case "show":
                    arrayStack.showStack();
                    break;
                case "push":
                    System.out.println("请输入你要添加的数:");
                    int value=scanner.nextInt();
                    arrayStack.stackPush(value);
                    break;
                case "pop": // 在里面抛出了异常,可能有异常,try-catch
                    try {
                        int res=arrayStack.stackPop();
                        System.out.printf("出栈的数据为%d\n",res);
                    }catch (Exception e){
                        System.out.println(e.getMessage());
                    }
                    break;
                case "exit":
                    scanner.close();
                    loop=false;
                    break;
                default:
                    break;
            }
        }
        System.out.println("程序退出");
    }
}

3、使用单向链表来模拟栈(有头结点的-头插法实现)

思路分析:
1、先创建个结点类,然后创建单向链表类,之后利用头结点,进行头插,完成进栈操作。
2、由于栈是先进后出,因此出栈也是头结点指向的那个结点。
3、遍历也是从链表头到尾完成。

创建结点类
代码如下:

// 创建一个Person结点类
class Person{
    public int num;
    public Person next;

    @Override
    public String toString() {
        return "Person{" +
                "num=" + num +
                '}';
    }

    public Person(int num) {
        this.num = num;
    }
}

创建栈(单向链表头插)
代码如下:

class ListStack{
    // 先创建一个头结点
    Person head =new Person(0);

    public void StackAdd(Person person){  // 添加操作 进栈

        person.next = head.next;
        head.next = person;

        }

    public Person StackDel(){   // 删除操作  出栈
        if (head.next==null){
            System.out.println("栈为空");
            return null;
        }else if (head.next.next==null){
            return head.next;
        }else {
            Person temp=head.next;  // 变量向保存要出栈的数据,以便return
            head.next=head.next.next;
            return temp;
        }
    }

    public void StackShow(){   // 显示操作 由于是头插,直接头部遍历
        Person temp=head;  // 遍历不能破坏链表 要移动辅助遍历temp
        if (temp.next==null){
            return;
        }
        while (true){
            if (temp.next==null){
                break;
            }
            System.out.println(temp.next);
            temp=temp.next;
        }
    }
}

测试如下:

public class ListStackDemo {
    public static void main(String[] args) {
        // 创建结点
        Person a = new Person(1);
        Person b = new Person(2);
        Person c = new Person(3);
        Person d = new Person(4);

        // 创建栈
        ListStack listStack = new ListStack();

        //添加数据 进栈
        listStack.StackAdd(a);
        listStack.StackAdd(b);
        listStack.StackAdd(c);
        listStack.StackAdd(d);

        System.out.println("进栈后的栈是---");
        //显示
        listStack.StackShow();

        // 删除数据 出栈
        Person deldata1=listStack.StackDel();
        System.out.println("出栈的元素是"+ deldata1);

        System.out.println("现在的栈是---");
        listStack.StackShow();

    }
}

二、栈的应用—表达式的计算

1、中缀表达式计算计算器

1.1 思路:

1、通过一个index索引,来遍历表达式
2、如果发现是个数字,就直接入数栈
3、如果发现是个符号,就分如下情况
① 如果当前符号栈为空,就直接入栈
②遇到界限符。遇到“ ”直接入符号栈;遇到“”则依次弹出栈内运算符,并弹出数栈两个数,进行计算,在保存在数栈中。直到弹出“(”为止。
③ 如果符号栈有操作符,就进行比较,如果当前操作符的优先级小于或等于栈中的操作符,就需要从数栈中pop处两个数,再从符号栈中pop出一个符号,进行运算,将得到的结果,入数栈,然后将当前的操作符入符号栈。
4、当表达式扫描完毕,就顺序的从数栈和符号栈中pop处相应的数和符号,并计算,
5、最后在数栈只有一个数字,就是表达式的结果

注意:

期间每弹出一个运算符,就需要在弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈。

1.2 代码实现

创建栈并增加(判定优先级、判定运算符、计算)等方法

// 先创建一个栈
// 定义一个ArrayStack2表示栈
class ArrayStack2{
    private int maxSize; // 栈的大小
    private int[] stack; // 数组模拟栈,数据就放在该数组
    private int top =-1; //top表示栈顶,初始化为-1

    // 构造器
    public ArrayStack2(int maxSize) {
        this.maxSize = maxSize;
        stack = new int[this.maxSize];  //初始化数组,
        // 并赋值给数组类型的变量stack
    }


    // 栈满
    public boolean isFull(){
        return top==maxSize-1;
    }

    // 栈空
    public boolean isEmpty(){
        return top==-1;
    }

    //入栈 push
    public void stackPush(int value){

        if (top==maxSize-1) {
            System.out.println("栈满---不能再添加");
            return;
        }
        top++;
        stack[top]=value;
    }

    // 出栈 pop,将栈顶的数据返回
    public int stackPop(){
        if (top==-1){
            // 抛出异常
            throw new RuntimeException("栈空,没有数据---");
        }
        int temp=stack[top]; // 记录下要出栈的值,
        top--;                    // 如果直接return,就不能完成top--了
        return temp;
    }

    // 观察栈顶的方法
    public int showTop(){
        if (top==-1) {
            System.out.println("栈空,没有数据---");
            return -1;
        }
        return stack[top];

    }


    // 遍历栈 从栈顶开始到栈底
    public void showStack(){
        if (top==-1){
            System.out.println("栈空,没有数据---");
            return;
        }
        for (int i=top;i>=0;i--){
            System.out.printf("stack[%d]=%d\n",i,stack[i]);
        }
    }

    // 返回运算符的优先级,优先级使用数字表示,数字越大,优先级越高
    public int priority(int oper){
        if (oper=='*'||oper=='/'){
            return 1;
        }else if(oper=='+'||oper=='-'){
            return 0;
        }else {
            return -1;// 假定目前的表达式只有+、-、*、/
        }
    }

    // 判断是否是个运算符
    public  boolean isOper(char val){
        return val=='+'||val=='-'||val=='*'||val=='/';
    }

    // 计算方法
    public int cal(int num1,int num2 ,int oper){
        int res=0; //res 用于存放计算结果
        switch (oper) {
            case '+':
                res = num1 + num2;
                break;
            case '-':
                res = num2 - num1; // 注意顺序
                break;
            case '*':
                res = num2 * num1;
                break;
            case '/':
                res = num2 / num1;
                break;
            default:
                break;
        }
        return res;
    }
}

测试代码如下:

public class Calculator {
    public static void main(String[] args) {
        String expression ="300+2*6-2*2";
        //创建两个栈,数栈和符号栈
        ArrayStack2 numStack = new ArrayStack2(10);
        ArrayStack2 operStack = new ArrayStack2(10);

        // 定义需要的相关变量  字符串不能用for直接遍历,故采用index索引扫描
        int index=0; // 用于扫描
        int num1=0;
        int num2=0;
        int oper=0;
        int res=0;
        char ch=' '; //将每次扫描得到的char保存到ch
        String keepnum=""; // 用于拼接多位数
        // 开始while循环的扫描experssion
        while (true){
            // 一次得到experssion的每一个字符
            // substring方法获取返回一个索引间的字符串,charAt获取第一个
            ch =expression.substring(index,index+1).charAt(0);
            //判断ch是什么
            if (operStack.isOper(ch)){ // 如果是运算符
                // 判断当前的符号栈是否为空
                if (!operStack.isEmpty()){ //如果不为空
                    if (operStack.priority(ch)<=operStack.priority(operStack.showTop())){
                        num1=numStack.stackPop();
                        num2=numStack.stackPop();
                        oper=operStack.stackPop();
                        res=numStack.cal(num1,num2,oper);
                        // 把运算的结果入数栈
                        numStack.stackPush(res);
                        // 然后将当前的操作符入符号栈
                        operStack.stackPush(ch);
                    }else {
                        // 如果当前操作符的优先级大于栈中的操作符,直接写入
                        operStack.stackPush(ch);
                    }
                }else {
                    // 如果符号栈为空,则直接入符号栈
                    operStack.stackPush(ch);
                }
            }else {  //如果是数,直接入数栈
                // numStack.stackPush(ch)是错的,因为是字符串要ascll码
                // numStack.stackPush(ch-48);
                // 分析思路
                // 1、当处理多位数时,不能发现是一个数就立即出栈,可能是个多位数
                // 2、在处理数,需要向expression表达式的index再看一位,如果是数就进行扫描,如果符号才入栈
                // 3、因此需要定义一个变量字符串,用于拼接

                // 处理多位数
                keepnum+=ch;

                // 如果ch已经是expression最后一位,就直接入栈
                if (index==expression.length()-1){
                    numStack.stackPush(Integer.parseInt(keepnum));
                }else {
                    // 判断下一个字符是不是数字,如果是数字,就继续扫描,如果是运算符,则入栈
                    // 注意只是看后一位,不是index++
                    if (operStack.isOper(expression.substring(index+1,index+2).charAt(0))){
                        //如果后一位是运算符,则入栈keepNum=”1“或”123“
                        numStack.stackPush(Integer.parseInt(keepnum));
                        //重要的  要把keeepnum清空
                        keepnum="";
                    }
                }

            }
            // 让index+1,并判断是否扫描到expression最后
            index++;
            if (index>=expression.length()){
                break;
            }
        }
        // 当表达式扫描完毕,就顺序的从数栈和符号栈中弹出相应的数和符号,并允许
        while (true){
            // 如果符号栈为空,最计算得到最后结果
            if (operStack.isEmpty()){
                break;
            }
            num1=numStack.stackPop();
            num2=numStack.stackPop();
            oper=operStack.stackPop();
            res=numStack.cal(num1,num2,oper);
            // 把运算的结果入数栈
            numStack.stackPush(res);
        }
        // 将最后的数,弹出
        System.out.printf("表达式%s=%d",expression,numStack.stackPop());
    }
}

2、中缀转后缀计算器

① 中缀表达式字符串(String)转中缀表达式集合(List)

思路分析:

由于字符串再转为后缀表达式过程中不好遍历,故把其转为Lsit中缀表达式。

代码如下:

注意:
① String.charAt(int index)方法获取当前索引位置对应的字符
② 字符与数字进行比较是拿ascii码进行的
③字符与数字计算 例如 ch+=2;或ch++; 得到的ch还是字符,而system.out.println(ch+2) 输出的是ascii码。

// 将中缀表达式转为对应的中缀List
    public static List<String> toInfixExpression(String arr) {
        ArrayList<String> ls = new ArrayList<>(); // 定义一个List,存放中缀对应的内容
        int index = 0;// 索引取遍历中缀表达式arr
        char ch; //将每次扫描得到的char保存到ch
        String str = ""; // 用于拼接多位数

        do {
            // 如果ch是一个非数字,需要加入到ls中
            // String.charAt(int index)方法获取当前索引位置对应的字符
            // 字符与数字进行比较是拿ascii码进行的
            // 字符与数字计算  例如 ch+=2;或ch++; 得到的ch还是字符
            //              而system.out.println(ch+2) 输出的是ascii码
            if ((ch = arr.charAt(index)) < 48 || (ch = arr.charAt(index)) > 57) {
                ls.add("" + ch);
                index++; //需要index后移
            } else { //如果是一个数,需要考虑多位数
                str = ""; // 先将str置成”“
                while (index < arr.length() && (ch = arr.charAt(index)) >= 48 && (ch = arr.charAt(index)) <= 57) {
                    str += ch; // 拼接
                    index++;
                }
                ls.add(str);
            }
        } while (index < arr.length());
        return ls; //返回
    }

② 中缀表达式转换后缀表达式 (将中缀List转后缀List)

具体步骤:

1、 初始化两个栈:运算符栈s1和储存中间结果的栈s2;
2、 从左至右扫描中缀表达式;
3、 遇到操作数时,将其压入s2;
4、遇到运算符时,比较其于s1栈顶运算符的优先级:
① 如果s1为空,或栈顶运算符为左括号”(“,则直接将此运算符入栈;
② 否则,若优先级比栈顶运算符的高,也将运算符压入s
1;
③ 否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(①)与此时的新栈顶运算符比较 (实际就是—弹出栈中优先级高于或等于当前运算符的所有运算符)
5、遇到界限符,遇到“”直接入栈;遇到“”则依次弹出栈内运算符并压入s2,直到弹出“”为止,此时将着一对括号丢弃。
6、重复步骤2-5,直到表达式的最右边
7、将s1中剩余的运算符依次弹出并压入s2
8、依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式。

代码如下:

说明:因为中间结果栈s2,在整个过程中,没有pop过程,而且后面还需要逆序输出,比较麻烦。因此采用List s2。

// 方法:将得到的中缀表达式List转为后缀表达式List
    public static  List<String> parseSuffixExpressionList(List<String> ls){
        // 定义两者栈
        Stack<String> s1 = new Stack<>();  // 符号栈
        // Stack<String> s2 = new Stack<>();  // 中间值栈
        // 说明 因为s2这个栈,在整个过程中,没有pop过程,而且后面还需要逆序输出,比较麻烦。
        // 这里就不用Stack<String> s2   直接用List<String> s2
        ArrayList<String> s2 = new ArrayList<>();


        // 遍历ls
        for(String item:ls){
            //如多是一个数 加入s2
            if (item.matches("\\d+")){
                s2.add(item);
            }else if (item.equals("(")){
                s1.push(item);
            }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新栈顶比较  (即 弹出所有大于等于的)
                // 缺少个比较优先级的类
                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
    }

优先级的比较方法如下:

// 编写一个类Operation可以返回一个运算符,对应的优先级
class  Operation{
    private static int ADD=1;
    private static int SUB=1;
    private static int MUL=1;
    private static int DIV=1;

    // 写一个方法,返回对应的优先级数字
    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:
                break;
        }
        return result;
    }
}

测试如下:

String expression = "1+((2+3)*4)-5";
        List<String> infixExpressionList = toInfixExpression(expression);
        System.out.println("中缀表达式对应的List"+infixExpressionList); // [1,+,(,(,2,+,3,),*,4,),-,5]
        List<String>  parseSuffixExpressionList = parseSuffixExpressionList(infixExpressionList);
        System.out.println("后缀表达式对应的List"+parseSuffixExpressionList); // [1,2,3,+,4,*,+,5,-]

③ 后缀表达式的计算
代码如下:

// 完成逆波兰的计算
    public static int calculate(List<String> ls){  //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
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值