数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器

简介: 栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。

前言

  • 学习栈
  • 了解栈的特点。先进后出,后进先出
  • 使用数组模拟栈、链表模拟栈。(相比于双链表和单向环形链表来说是相对简单的
  • 栈的应用实例:用栈+中缀表达式 实现综合计算器
  • 2020.4.21、22 日学习
  • 相对于前面的双链表和单向环形链表,这里的数组模拟栈、链表模拟栈是比较简单的。
  • 比较重要的是 栈的应用:栈+中缀表达式 实现综合计算器。关于中缀在下一节博客学习、讲解。

一、栈

1.1 栈的介绍

  1. 栈的英文为(stack)
  2. 栈是一个 先入后出 (FILO-First In Last Out)的有序列表。
  3. 栈(stack)是限制线性表中元素的插入和删除 只能在线性表的同一端 进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为 栈顶 (Top),另一端为固定的一端,称为 栈底(Bottom)。
  4. 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除。
  5. 栈的基础方法为入栈、出栈、显示栈数据

1.2 栈的应用实例【重点】

请输入一个表达式
计算式:[7_2_2-5+1-5+3-3] 点击计算【如下图】
在这里插入图片描述
请问: 计算机底层是如何运算得到结果的? 注意不是简单的把算式列出运算,因为我们看这个算式 7 * 2 * 2 - 5, 但是计算机怎么理解这个算式的(对计算机而言,它接收到的就是一个 字符串 ),我们讨论的是这个问题。-> 栈

1.3 栈的应用场景

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

1.4 入栈与出栈

直接上图,看图理解:
在这里插入图片描述
在这里插入图片描述

二、数据模拟栈

2.1 思路分析

  1. 由于栈是一种有序列表,当然可以使用数组的结构来储存栈的数据内容,下面我们就用数组模拟栈的出栈,入栈等操作
  2. 实现思路分析,并画出示意图
  1. 使用数组来模拟栈
  2. 定义一个 top 来表示栈顶,初始化 为 -1
  3. 入栈 的操作,当有数据加入到栈时, top++; stack[top] = data;
  4. 出栈 的操作,int value = stack[top]; top--, return value

在这里插入图片描述

2.2 代码结构

ArrayStack: 数组模拟栈的 类
ArrayStackMain:测试类
在这里插入图片描述

2.3 ArrayStack 栈类

package com.feng.ch06_stack.s1_arraystack;

/*
 * 定义一个 ArrayStack 表示栈,数组模拟 栈
 * */
public class ArrayStack {

    private int maxSize; // 栈的大小
    private int[] stack; // 数组,数组模拟栈,数据就放在数组中。
    private int top = -1; // top 表示栈顶,初始化为-1

    public ArrayStack(int maxSize) {
        this.maxSize = maxSize;
        this.stack = new int[this.maxSize];
    }

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

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

    // 入栈
    public void push(int value){
        if (isFull()){
            System.out.println("栈已满,无法添加~");
            return;
        }
        top++;
        stack[top] = value;
    }

    // 出栈,将栈顶的数据返回
    public int pop(){
        if (isEmpty()){
            throw new RuntimeException("栈已空,无法获取数据\n");
        }
        int num = stack[top];
        top--;
        return num;
    }

    // 显示栈的情况【遍历栈】,遍历时,需要从栈顶开始显示数据
    public void list(){
        if (isEmpty()){
            System.out.printf("栈空,无数据\n");
        }
        for (int i = top; i>=0; i--){
            System.out.printf("stack[%d]=%d\n", i, stack[i]);
        }
    }
}

2.4 ArrayStackMain 测试类

package com.feng.ch06_stack.s1_arraystack;

import java.util.Scanner;

public abstract class ArrayStackMain {
    public static void main(String[] args) {
        // 测试 ArrayStack 是否正确
        // 先创建一个 ArrayStack 对象 -》栈
        ArrayStack stack = new ArrayStack(4);
        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":
                    stack.list();
                    break;
                case "push":
                    System.out.println("请输入一个数:");
                    int value = scanner.nextInt();
                    stack.push(value);
                    break;
                case "pop":
                    try {
                        int res = stack.pop();
                        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.printf("程序退出成功~~~");
    }
}

2.5 测试结果

简单运行测试一下,运行即可
在这里插入图片描述

三、单链表模拟栈

3.1 方法分析

栈的基础方法为入栈(push)、出栈(pop)、显示栈数据(list)

  1. 先创建头节点head。
  2. push():第一次添加时,直接添加到头结点的后面:head.setNext(newNode);
    第二次往后,先将头结点后面的节点挂载到newNode上,再将newNode挂载到head 上。即:
    newNode.setNext(head.getNext());
    head.setNext(newNode);
  3. pop():直接输出头结点head后的一个节点即可,再将后一个节点的后一个节点挂载到 头节点即可:
    StackNode next = head.getNext();
    head.setNext(next.getNext());
    return next;
  4. list():设置辅助指针。循环遍历输出即可。

3.2 代码结构

StackNode: 链表节点
LinkedStack: 栈方法
LinkedStackMain: 测试方法
在这里插入图片描述

3.3 StackNode 链表节点

package com.feng.ch06_stack.s2_linkedstack;

public class StackNode {
    private int no;
    private StackNode next;

    public StackNode(int no) {
        this.no = no;
    }
    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public StackNode getNext() {
        return next;
    }

    public void setNext(StackNode next) {
        this.next = next;
    }

    @Override
    public String toString() {
        return "StackNode{" +
                "no=" + no +
                '}';
    }
}

3.4 LinkedStack 栈方法

package com.feng.ch06_stack.s2_linkedstack;

public class LinkedStack {
    private StackNode head = new StackNode(0);

    public StackNode getHead() {
        return head;
    }

    /*
     * 入栈
     * */
    public void push(StackNode newNode) {
        if (head.getNext() == null) { // 第一次天机
            head.setNext(newNode);
        } else {
            newNode.setNext(head.getNext());
            head.setNext(newNode);
        }
    }

    /*
     * 出栈
     * */
    public StackNode pop(){
        if (head.getNext() == null){
            throw new RuntimeException("栈为空,无法出栈");
        }
        /*
        * 两种返回方式
        * */
//        int value = head.getNext().getNo();
//        head.setNext(head.getNext().getNext());
//        return value;
        StackNode next = head.getNext();
        head.setNext(next.getNext());
        return next;
    }

    /*
     * 遍历
     * */
    public void list(){
        if (head.getNext() == null){
            System.out.println("栈为空,无法遍历");
            return;
        }
        StackNode temporary = head.getNext();
        while (true){
            if (temporary == null){
                break;
            }
            System.out.printf("节点编号%d\n",temporary.getNo());
            temporary = temporary.getNext();
        }
    }
}

3.5 LinkedStackMain 测试方法

package com.feng.ch06_stack.s2_linkedstack;

public class LinkedStackMain {
    public static void main(String[] args) {
        LinkedStack stack = new LinkedStack();

        StackNode node1 = new StackNode(1);
        StackNode node2 = new StackNode(2);
        StackNode node3 = new StackNode(3);
        StackNode node4 = new StackNode(4);
        StackNode node5 = new StackNode(5);

        stack.push(node1);
        stack.push(node2);
        stack.push(node3);
        stack.push(node4);
        stack.push(node5);

        System.out.println("初始化的栈:");
        stack.list();

        // 测试出栈
        System.out.println();
        System.out.println("出栈的数据:"+stack.pop());

        System.out.println();
        System.out.println("出栈后的栈:");
        stack.list();
    }
}

3.5 测试结果

在这里插入图片描述

四、栈+中缀表达式 实现综合计算器

4.1 需求描述

给定一个表达式字符串:【3+2*6-2】, 进行计算。

中缀表达式 就是 3+2*6-2 这种我们熟知的计算表达式。关于中缀表达式在下一节详细讲解

4.2 思路分析

  1. 使用上面开发的数组模拟栈,新建两个栈,一个数栈、一个符号栈
  2. 通过一个 index 值(索引),来遍历我们的表达式
  3. 如果我们 发现是一个数字, 就直接入数栈
  4. 如果发现 扫描到是一个符号, 就分如下情况
    4.1 如果发现当前的符号栈为 空,就直接入栈
    4.2 如果符号栈有操作符,就进行比较, 如果 当前的操作符的优先级小于或者等于栈中的操作符 , 就需要从数栈中pop出两个数,在从符号栈中pop出一个符号,进行运算,将得到结果,入数栈,然后将当前的操作符入符号栈, 如果当前的操作符的优先级大于栈中的操作符, 就直接入符号栈
  5. 当表达式扫描完毕,就顺序的从 数栈和符号栈中pop出相应的数和符号,并运行.
  6. 最后在数栈只有一个数字,就是表达式的结果

验证: 3+2*6-2 = 13
在这里插入图片描述

4.3 代码实现

  • 先实现一位数的运算。
  • 扩展到多位数的运算, 扩展多位的运算,主要改变的地方:
  1. 增加了一个变量keepNum 用户多位数的拼接,
  2. 然后在扫描时,遇到的字符 为数字时,再往后多看一位,如果是数字,则拼接,如果不是数组,则到此结束。

4.3.1 Calculator.java 计算类

package com.feng.ch06_stack.s3_calculator;

public class Calculator {
    public static void main(String[] args) {
        // 根据前面思路,完成表达式的运算
        String expression = "3+2*6-2"; // 3+2*6-2=13
        // 思考:如果处理多位数的问题? 30+2*6-2=40 。添加 keepNum 用于拼接多位数。

        // 创建两个栈,数栈、一个符号栈
        ArrayStack numStack = new ArrayStack(10);
        ArrayStack operStack = new ArrayStack(10);
        // 定义需要的相关变量
        int index = 0; // 用于扫描
        int num1 = 0;
        int num2 = 0;
        int oper = 0;
        int res = 0;
        char ch = ' '; // 将每次扫描得到插入保存到 ch
        String keepNum = ""; // 用于拼接多位数
        // 开始循环的扫描 expression
        while (true) {
            // 依次得到expression 的每一个字符
            ch = expression.substring(index, index + 1).charAt(0);
            /*
            * 判断 ch 是什么,然后做相应的处理
            * 如果是运算符
            * */
            if (operStack.isOperation(ch)) {
                // 判断当前的符号栈是否为空
                if (!operStack.isEmpty()) {
                    // 如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符,就需要从数栈中 pop 出两个数,
                    // 在符号栈中 pop 出一个符号,进行运算,将得到结果,入数栈,然后将当前的操作符入符号栈。
                    if (operStack.priority(ch) <= operStack.priority(operStack.peek())) {
                        oper = operStack.pop();
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        res = numStack.calculation(num1, num2, oper);
                        // 把运算的结果入数栈
                        numStack.push(res);
                        // 然后把当前的操作符入符号栈
                        operStack.push(ch);
                    } else {
                        // 如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈
                        operStack.push(ch);
                    }
                } else {
                    // 如果为空,直接入栈。。
                    operStack.push(ch);
                }
            } else {// 如果是数
                /*
                * 如果是数,就直接入数栈, 这样不能满足 多位数。。。
                * // ? "1+3" '1' => 1, ch 为字符,转换为 数字要 减48。
                * 如果是个位数,用字符 char 来表示,则要减48,如果是多位数,下边变成了字符串,就直接使用包装类的方法转换成 整型
                * */
                //numStack.push(ch-48);

                /*
                 * 解决多位数的 分析思路:
                 * 1、当处理多位数时,不能发现是一个数就立即入栈,因为他可能是多位数
                 * 2、在处理数时,需要向expression 的表达式的index 后在多看一位,如果是数就立刻扫描,如果是符号在入栈
                 * 3、因此定义一个 变量 字符串,用于拼接
                 * */

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

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

            // 让index +1 ,并判断 是否扫描到 expression 最后
            index++;
            if (index >= expression.length()) {
                break;
            }

        }
        /*
        * 当表达式扫描完毕,就顺序的从数栈 和符号栈 中 pop 出相应的数和符号,并运行
        * */
        while (true) {
            // 如果符号栈为空,则计算到最后的结果,数栈中只有一个数字【结果】
            if (operStack.isEmpty()) {
                break;
            }
            num1 = numStack.pop();
            num2 = numStack.pop();
            oper = operStack.pop();
            res = numStack.calculation(num1, num2, oper);
            numStack.push(res);
        }
        // 将数栈的最后数,pop出,就是结果
        int result = numStack.pop();
        System.out.printf("表达式 %s = %d", expression, result);
    }
}

4.3.2 ArrayStack.java 使用的栈(自定义,数组模拟栈)

对于之前的数组模拟栈、链表模拟栈,这里需要增加几个方法:
peek(): 查看栈顶数据,但不是出栈。
priority(int oper):返回运算符的优先级,优先级是程序员来确定,优先级使用数字表示。
calculation(int num1, int num2, int oper):计算方法

package com.feng.ch06_stack.s3_calculator;

/*
 * 先创建一个栈,直接使用前面创建好的,
 * 需要扩展功能:增加
 * 定义一个 ArrayStack 表示栈
 * */
public class ArrayStack {

    private int maxSize; // 栈的大小
    private int[] stack; // 数组,数组模拟栈,数据就放在数组中。
    private int top = -1; // top 表示栈顶,初始化为-1

    public ArrayStack(int maxSize) {
        this.maxSize = maxSize;
        this.stack = new int[this.maxSize];
    }

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

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

    // 入栈
    public void push(int value) {
        if (isFull()) {
            System.out.println("栈已满,无法添加~");
            return;
        }
        top++;
        stack[top] = value;
    }

    // 出栈,将栈顶的数据返回
    public int pop() {
        if (isEmpty()) {
            throw new RuntimeException("栈已空,无法获取数据\n");
        }
        int num = stack[top];
        top--;
        return num;
    }

    // 显示栈的情况【遍历栈】,遍历时,需要从栈顶开始显示数据
    public void list() {
        if (isEmpty()) {
            System.out.printf("栈空,无数据\n");
        }
        for (int i = top; i >= 0; i--) {
            System.out.printf("stack[%d]=%d\n", i, stack[i]);
        }
    }

    /*
    * 查看栈顶的 数据, 不是真正出栈
    * */
    public int peek(){
        return stack[top];
    }

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

    /*
     * 判断是不是一个云算法
     * */
    public boolean isOperation(char value) {
        return value == '+' || value == '-' || value == '*' || value == '/';
    }

    /*
    * 计算方法
    * @param num1
    * @param num2
    * @param oper
    * @return
    * */
    public int  calculation(int num1, int num2, int oper){
        int res = 0;
        switch (oper){
            case '+':
                res = num1 + num2;
                break;
            case '-':
                res = num2 - num1; // 注意顺序
                break;
            case '*':
                res = num1 * num2;
                break;
            case '/':
                res = num1/num2;
                break;
            default:
                break;
        }
        return res;
    }
}
相关文章
|
4月前
|
机器学习/深度学习 算法 数据挖掘
没发论文的注意啦!重磅更新!GWO-BP-AdaBoost预测!灰狼优化、人工神经网络与AdaBoost集成学习算法预测研究(Matlab代码实现)
没发论文的注意啦!重磅更新!GWO-BP-AdaBoost预测!灰狼优化、人工神经网络与AdaBoost集成学习算法预测研究(Matlab代码实现)
168 0
|
3月前
|
机器学习/深度学习 运维 算法
【微电网多目标优化调度】多目标学习者行为优化算法MOLPB求解微电网多目标优化调度研究(Matlab代码实现)
【微电网多目标优化调度】多目标学习者行为优化算法MOLPB求解微电网多目标优化调度研究(Matlab代码实现)
229 1
|
6月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
135 0
栈区的非法访问导致的死循环(x64)
|
9月前
|
算法 数据可视化 开发者
为什么要学习数据结构与算法
今天,我向大家介绍一门非常重要的课程——《数据结构与算法》。这门课不仅是计算机学科的核心,更是每一位开发者从“小白”迈向“高手”的必经之路。
为什么要学习数据结构与算法
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
1044 9
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
302 59
|
11月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
543 77
|
10月前
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
247 11
|
10月前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。

热门文章

最新文章