数据结构与算法之栈

1>栈的基本介绍

1.1、栈的实际需求

请计算表达式:[722-5+1-5+3-3] 的值
请问: 计算机底层是如何运算得到结果的?
 注意不是简单的把算式列出运算,因为我们看这个算式 7 * 2 * 2 - 5,
 但是计算机怎么理解这个算式的
对计算机而言, 它接收到的就是一个字符串, 我们讨论的是这个问题:栈

在这里插入图片描述

1.2、栈的基本性质

栈的英文为(stack)
栈是一个先入后出(FILO-First In Last Out)的有序列表
栈(stack)是限制线性表中元素的插入和删除只能在线性表的
同一端进行的一种特殊线性表。
允许插入和删除的一端,为变化的一端,称为栈顶(Top),
另一端为固定的一端, 称为栈底(Bottom)。
根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,
而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除
图解方式说明出栈(pop)和入栈(push)的概念

在这里插入图片描述

1.3、栈的应用场景

  • 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中, 直到子程序执行完后再将地址取出,以回到原来的程序中。
  • 处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外, 也将参数、 区域变量等数据存入栈中。
  • 表达式的转换:[中缀表达式转后缀表达式]与求值(实际解决)。
  • 二叉树的遍历。
  • 图形的深度优先(depth 一 first)搜索法。

2>数组模拟栈

2.1、代码思路

  • maxSize :栈的大小(数组的大小)
  • arr :用来模拟栈的数组
  • top :指向当前栈顶元素,初始值为 -1 ,表示栈空
  • 判断栈满:top == maxSize ,即已经到达数组最后一个位置
  • 判断栈空:top == -1
  • 入栈:arr[++top] = arr;
  • 出栈:return arr[top–] ;
    在这里插入图片描述

2.2、代码实现

package com;

public class StackNode {
    public int maxsize;
    public int[] stack;
    public int top = -1;

    public StackNode(int maxsize)
    {
        this.maxsize = maxsize;
        stack = new int[maxsize];
    }
    public boolean isFull()
    {
        return top == maxsize-1;
    }
    public boolean isEmpty()
    {
        return top == -1;
    }
    public void push(int data)
    {
        if (isFull())
        {
            System.out.println("栈满");
            return;
        }
        stack[++top] = data;
    }
    public int pop()
    {
        if (isEmpty()){
            throw new RuntimeException("栈空,无法pop出元素");
        }
        return stack[top--];
    }
    public void list()
    {
        if (isEmpty())
            throw new RuntimeException("栈空,无法遍历元素");
        for (int i = top;i>=0;i--)
        {
            System.out.printf("stack[%d] = %d\n",i,stack[i]);
        }
    }
}
package com;

import java.util.Scanner;

public class Test03 {
    public static void main(String[] args) {
        StackNode stackNode = new StackNode(4);
        Scanner scanner = new Scanner(System.in);
        String key = "";
        boolean k = true;
        while (k) {
            System.out.println("show:表示显示栈");
            System.out.println("exit:退出程序");
            System.out.println("push:入栈");
            System.out.println("pop:出栈");
            key = scanner.next();
            switch (key) {
                case "show":
                    stackNode.list();
                    break;
                case "exit":
                    k = false;
                    break;
                case "push":
                    System.out.println("请输出一个数据");
                    int value = scanner.nextInt();
                    stackNode.push(value);
                    break;
                case "pop":
                    System.out.println("出栈数据为" + stackNode.pop());
                    break;
                default:
                    break;
            }
        }
        System.out.println("程序已退出");
    }
}

2.3>课后作业

使用链表模拟栈

3>栈实现综合计算器(中缀表达式)

3.1、代码思路

  • 栈分为两个栈:
    • 数栈(numStack):存储表达式中的数字
    • 符号栈(operStack):存储表达式中的符号
  • 扫描表达式(这里并没有考虑括号):
    • 对于数:扫描到数,则直接压入数栈
    • 对于运算符:扫描到运算符,分为如下几种情况:
      • 如果符号栈为空,则直接入栈
      • 如果符号栈不为空:
        • 如果当前扫描到的运算符的优先级 <= 符号栈栈顶的运算符的优先级,说明上次的运算符优先级较高,先执行优先级高的运算
          • 从数栈中弹出两个数,根据符号栈栈顶的运算符进行运算(优先级高,就先算出来)
          • 然后将计算的结果压入栈中
          • 再将当前运算符压入符号栈
        • 如果当前扫描到的运算符的优先级 > 符号栈栈顶的运算符的优先级,说明上次的运算符优先级较低,直接压入符号栈
  • 何时停止循环?
    • 处理完表达式,退出循环
    • 即表达式下标(index)的值大于表达式(expression)的长度
    • 代码:index >= expression.length()
  • 表达式扫描完成:
    • 此时符号栈中的运算符优先级都相同
    • 从数栈中弹出两个数,再从符号栈中弹出一个运算符,进行运算,计算结果放回数栈中
    • 何时停止循环?符号栈为空则停止:operStack.isEmpty()
    • 表达式的值?符号栈为空时,数栈栈顶还有一个元素,这个元素的值就是表达式的值
  • 举例:3+2*6-2
  • 首先
    - 将 3 压入数栈
    - 将 + 压入符号栈
    - 将 2 压入数栈
2
3	  +
数栈	符号栈
  • 由于:优先级大于 + ,所以将 * 压入 符号栈,然后将 6 压入数栈
6
2	  *
3	  +
数栈	符号栈
  • 由于 - 优先级低于 * ,所以从数栈中弹出两个数(6 和 2),从符号栈中弹出一个运算符(*),进行运算,运算结果再压入数栈,然后将 - 压入符号栈
12	  -
3	  +
数栈	符号栈
  • 将 2 压入数栈,表达式处理完毕
2
12	  -
3	  +
数栈	符号栈
  • 重复此过程,直至符号栈为空:从数栈中弹出两个数,再从符号栈中弹出一个运算符,进行运算,计算结果放回数栈中
10
3	  +
数栈	符号栈
13
数栈	符号栈

在这里插入图片描述

3.2、代码实现

栈的定义:专为计算器而生的栈
对于乘除法特别说明:
由于栈先进后出的特点,
num1 是运算符后面的数(减数、除数),
num2 是运算符前的数(被减数、被除数),
特别需要注意减法与除法的顺序
res = num2 - num1;
res = num2 / num1;
package com;

public class CalStack {
    public int maxsize;
    public int[] stack;
    public int top = -1;

    public CalStack(int maxsize)
    {
        this.maxsize = maxsize;
        stack = new int[maxsize];
    }
    public boolean isEmpty()
    {
        return top == -1;
    }
    public boolean isFull()
    {
        return top == maxsize - 1;
    }
    public void push(int data)
    {
        if (isFull())
        {
            throw new RuntimeException("栈已满");
        }
        stack[++top] = data;
    }
    public int pop()
    {
        if (isEmpty())
            throw new RuntimeException("栈是空的");
        return stack[top--];
    }
    public int priority(int oper)
    {
        if (oper == '*' || oper == '/'){
            return 1;}
        else if (oper == '+' || oper == '-') {
            return 0;}
        else {
            return -1;
        }
    }
    public int cal(int num1,int num2,int oper)
    {
        int value =0;
        switch (oper)
        {
            case '+':
                value = num1 + num2;
                break;
            case '-':
                value = num2 - num1;
                break;
            case '*':
                value = num1 * num2;
                break;
            case '/':
                value = num2 / num1;
                break;
        }
        return value;
    }
    public boolean isOper(char c)
    {
        return c == '+'||c == '-'||c == '*'||c =='/';
    }
    public int peek()
    {
        if (isEmpty())
            throw new RuntimeException("栈为空,栈顶无元素");
        return stack[top];
    }
}
package com;

public class Test04 {
    public static void main(String[] args) {
        CalStack numStack = new CalStack(10);
        CalStack opeStack = new CalStack(10);
        char c = ' ';
        char d = ' ';
        int e = 0;
        String s = "";
        int num1 = 0;
        int num2 = 0;
        int index = 0;
        String expression = "70*2*2-5+1-5+3-4";
        int size = expression.length();
        while (true) {
            c = expression.charAt(index);
            if (index == size - 1) {
                int f = Integer.parseInt(String.valueOf(c));
                System.out.println(f);
                numStack.push(f);
                System.out.println(numStack.peek());
                break;
            }
            if (opeStack.isOper(c)) {
                if (opeStack.isEmpty())
                    opeStack.push(c);
                else {
                    if (opeStack.priority(c) > opeStack.priority(opeStack.peek())) {
                        opeStack.push(c);
                    } else {
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        e = opeStack.pop();
                        opeStack.push(c);
                        numStack.push(numStack.cal(num1, num2, e));
                        //System.out.println(numStack.cal(num1, num2, e));
                    }
                }
                //System.out.println(c);
                index++;
            } else {
                s += c;
                d = expression.charAt(index + 1);
                if (opeStack.isOper(d)) {
                    numStack.push(Integer.parseInt(s));
                    s = "";
                }
                //numStack.push(Integer.parseInt(s));
                index++;
            }
        }
        while (true)
        {
            if (opeStack.isEmpty())
            {
                System.out.printf("%s的结果是%d",expression,numStack.pop());
                break;
            }
            num1 = numStack.pop();
            //System.out.println("num1 = " +num1);
            num2 = numStack.pop();
            e = opeStack.pop();
            //System.out.println(numStack.cal(num1,num2,e));
            numStack.push(numStack.cal(num1,num2,e));
        }
    }
}

4>前缀 中缀 后缀表达式

4.1、前缀表达式(波兰表达式)

4.1.1、前缀表达式

  • 前缀表达式又称波兰式,前缀表达式的运算符位于操作数之前
  • 举例说明: (3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6

4.1.2、前缀表达式的计算机求值

  • 从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素 和 次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果
  • 例如:(3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6 , 针对前缀表达式求值步骤如下:
    • 从右至左扫描,将6、5、4、3压入堆栈
    • 遇到+运算符,因此弹出3和4(3为栈顶元素,4为次顶元素),计算出3+4的值,得7,再将7入栈
    • 接下来是×运算符,因此弹出7和5,计算出7×5=35,将35入栈
    • 最后是-运算符,计算出35-6的值,即29,由此得出最终结果

4.2、中缀表达式

  • 中缀表达式就是常见的运算表达式,如(3+4)×5-6
  • 中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作(前面我们讲的案例就能看的这个问题,因为中缀表达式存在运算符优先级的问题),因此,在计算结果时,往往会将中缀表达式转成其它表达式来操作(一般转成后缀表达式)

4.3、后缀表达式

4.3.1、后缀表达式

  • 后缀表达式又称逆波兰表达式,与前缀表达式相似,只是运算符位于操作数之后
  • 中缀表达式举例说明: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 –
  • 再比如:
正常的表达式逆波兰表达式
a+ba b +
a+(b-c)a b c - +
a+(b-c)*da b c – d * +
a+d*(b-c)a d b c - * +
a=1+3a 1 3 + =

4.3.2、后缀表达式的计算机求值

  • 从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 和 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果
  • 例如: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 - , 针对后缀表达式求值步骤如下:
    • 从左至右扫描,将3和4压入堆栈;
    • 遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
    • 将5入栈;
    • 接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
    • 将6入栈;
    • 最后是-运算符,计算出35-6的值,即29,由此得出最终结果

5>逆波兰计算器

5.1、计算器说明

  • 输入一个逆波兰表达式(后缀表达式), 使用栈(Stack),计算其结果
  • 支持小括号和多位数整数, 因为这里我们主要讲的是数据结构, 因此计算器进行简化, 只支持对整数的计算

5.2、代码思路

  • 计算后缀表达式无需考虑运算符优先级问题,所以只需要一个数栈即可
  • 分为两种情况:
    • 遇到数:压入数栈
    • 遇到运算符:从数栈中弹出两个数,进行计算,计算结果压入数栈
  • 何时计算完成?处理完表达式就代表计算完成

5.3、代码实现

  • 出栈的两个数:num2 和 num1
    • num2 先出栈,所以 num2 是减数或除数
    • num1 后出栈,所以 num1 是被减数或被除数
public class Test05 {
    public static void main(String[] args) {
        String suffixExpression = "4 5 * 8 - 60 + 8 2 / +"; // 76
        String[] split = suffixExpression.split(" ");
        List<String> list = new ArrayList<>();
        for (String s : split) {
            list.add(s);
        }
        for (String s:list) {
            System.out.println(s);
        }
        System.out.printf("后缀表达式%s的结果为%d", suffixExpression, cal(list));
        //System.out.printf("后缀表达式%s的结果为%d",expression,cal(list));
    }
        public static int cal(List<String> list) {
        Stack<String> stack = new Stack<>();
        int num1 = 0;
        int num2 = 0;
        int result = 0;
        for (String s : list) {
            if (s.matches("\\d+")) {
                stack.push(s);
            } else {
                num1 = Integer.parseInt(stack.pop());
                num2 = Integer.parseInt(stack.pop());
                switch (s) {
                    case "+":
                        result = num1 + num2;
                        break;
                    case "-":
                        result = num2 - num1;
                        break;
                    case "*":
                        result = num1 * num2;
                        break;
                    case "/":
                        result = num2 / num1;
                        break;
                }
                stack.push(""+result);
            }
        }
        return Integer.parseInt(stack.pop());
    }
  }

6>中缀表达式转后缀表达式

6.1、代码思路

基于堆栈的算法

  • 1、从左到右扫描每一个字符。如果扫描到的字符是操作数(如a、b等),就直接输出这些操作数。

c++堆栈的应用:中缀表达式转化为后缀表达式

  • 2、如果扫描到的字符是一个操作符,分三种情况:
    • (1)如果堆栈是空的,直接将操作符存储到堆栈中(push it)
    • (2)如果该操作符的优先级大于堆栈出口的操作符,就直接将操作符存储到堆栈中(push it)
    • (3)如果该操作符的优先级低于堆栈出口的操作符,就将堆栈出口的操作符导出(pop it),
      直到该操作符的优先级大于堆栈顶端的操作符。将扫描到的操作符导入到堆栈中(push)。
      在这里插入图片描述
      在这里插入图片描述
  • 3、如果遇到的操作符是左括号"(”,就直接将该操作符输出到堆栈当中。该操作符只有在遇到右括号“)”的时候移除。这是一个特殊符号该特殊处理。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 4、如果扫描到的操作符是右括号“)”,将堆栈中的操作符导出(pop)到output中输出,直到遇见左括号“(”。将堆栈中的左括号移出堆栈(pop)。继续扫描下一个字符

  • 5、如果输入的中缀表达式已经扫描完了,但是堆栈中仍然存在操作符的时候,我们应该讲堆栈中的操作符导出并输入到output 当中。

在这里插入图片描述
在这里插入图片描述

6.2、举例说明

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

在这里插入图片描述

6.3、代码实现

public class Test05 {
    public static void main(String[] args) {
        String expression = "1+((2+3)*4)-5";
        List<String> list = new ArrayList<>();
        list = change(expression);
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()!=false)
        {
            System.out.println(iterator.next());
        }
        System.out.printf("中缀表达式%s的结果为%d", expression, cal(list));
        
    public static List<String> change(String expression) {
        List<String> list = new ArrayList<>();
        Stack<Character> stack = new Stack<>();
        int size = expression.length();
        int index = 0;
        char c = ' ';
        char d = ' ';
        String s = "";
        while(true)
        {
            c = expression.charAt(index);
            if (index == size-1)
            {
                list.add(String.valueOf(c));
                while (stack.isEmpty()!=true)
                {
                    list.add(String.valueOf(stack.pop()));
                }
                break;
            }
            if (c<48||c>57)
            {
            if (stack.isEmpty() && c !=')')
            {
                stack.push(c);
            }
            else {
                if (c == '(') {
                    stack.push(c);
                } else if (c == ')') {
                    while (true) {
                        d = stack.pop();
                        if (d == '(') {
                            break;
                        }
                        list.add(String.valueOf(d));
                    }
                } else if (stack.peek() == '(') {
                    stack.push(c);
                } else if (priority(stack.peek()) >= priority(c)) {
                    while (true) {
                        if (stack.isEmpty() || priority(c) > priority(stack.peek()))
                            break;
                        list.add(String.valueOf(stack.pop()));
                    }
                    stack.push(c);
                }
            }
            index++;
            }
            else {
                s+=c;
                if (expression.charAt(index+1)<48||expression.charAt(index+1)>57)
                {
                    list.add(s);
                    s = "";
                }
                index++;
            }
        }
        return list;
    }

    public static int priority(char s)
    {
        if (s == '*'||s == '/')
        {
            return 1;
        }
        else if (s == '+'||s == '-')
        {
            return 0;
        }
        else {
            return -1;
        }
    }

    public static int cal(List<String> list) {
        Stack<String> stack = new Stack<>();
        int num1 = 0;
        int num2 = 0;
        int result = 0;
        for (String s : list) {
            if (s.matches("\\d+")) {
                stack.push(s);
            } else {
                num1 = Integer.parseInt(stack.pop());
                num2 = Integer.parseInt(stack.pop());
                switch (s) {
                    case "+":
                        result = num1 + num2;
                        break;
                    case "-":
                        result = num2 - num1;
                        break;
                    case "*":
                        result = num1 * num2;
                        break;
                    case "/":
                        result = num2 / num1;
                        break;
                }
                stack.push(""+result);
            }
        }
        return Integer.parseInt(stack.pop());
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值