【数据结构】栈结构与应用

简介: 【数据结构】栈结构与应用

一、栈的概述

1、栈的介绍

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

2、栈的应用场景

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

3、栈的代码实现

1)数组实现

package work.rexhao.stack;

public class arrayStackdemo {
   

    public static void main(String[] args) {
   
        arrayStact as = new arrayStact(10);
        as.push(1);
        as.push(2);
        as.list();
        System.out.println(as.pop());
        System.out.println(as.pop());
        as.list();
    }

}

class arrayStact {
   
    int maxSize;
    int[] data;
    int top;

    /**
     * 栈的构造器
     * 
     * @param maxSize 栈的大小
     */
    arrayStact(int maxSize) {
   
        this.maxSize = maxSize;
        data = new int[maxSize];
        top = -1;
    }

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

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

    /**
     * 弹出
     */
    public int pop() {
   
        if (isEmpty()) {
   
            System.out.println("栈空");
            return -1;
        }
        return data[top--];
    }

    /**
     * 压栈
     */
    public void push(int num) {
   
        if (isFull()) {
   
            System.out.println("栈满");
        } else {
   
            data[++top] = num;
        }
    }

    /**
     * 遍历
     */
    public void list() {
   
        if (isEmpty()) {
   
            System.out.println("栈空");
            return;
        }
        for (int i = 0; i <= top; i++) {
   
            System.out.printf("data[%d] = %d\n", i, data[i]);
        }
    }
}

2)链表实现

package work.rexhao.stack;

public class linkedListStackDemo {
   
    public static void main(String[] args) {
   
        linkedListStack lls = new linkedListStack();
        try {
   
            lls.pop();
        } catch (Exception e) {
   
            System.out.println(e.getMessage());
        }
        lls.push(1);
        lls.push(2);
        lls.pop();
        lls.push(3);
        lls.push(4);
        lls.show();
    }
}

class linkedListStack {
   
    private stackNode head = new stackNode(-1);

    /**
     * 压栈
     */
    public void push(int num) {
   
        if (head.next == null) {
   
            head.next = new stackNode(num);
            return;
        }
        stackNode temp = head.next;
        while (true) {
   
            if (temp.next == null) {
   
                temp.next = new stackNode(num);
                return;
            }
            temp = temp.next;
        }

    }

    /**
     * 出栈
     */
    public int pop() {
   
        if (isEmpty()) {
   
            throw new RuntimeException("栈空");
        }
        stackNode temp = head;
        while (true) {
   
            if (temp.next.next == null) {
   
                break;
            }
            temp = temp.next;
        }
        int ans = temp.next.getData();
        temp.next = null;
        return ans;
    }

    /**
     * 判空
     */
    public boolean isEmpty() {
   
        return head.next == null;
    }

    /**
     * 遍历
     */
    public void show() {
   
        if (isEmpty()) {
   
            return;
        }
        stackNode temp = head.next;
        while (true) {
   
            if (temp == null) {
   
                return;
            }
            System.out.println(temp.toString());
            temp = temp.next;
        }
    }
}

class stackNode {
   
    private int data;
    public stackNode next;

    stackNode(int data) {
   
        this.data = data;
    }

    public int getData() {
   
        return data;
    }

    public void setData(int data) {
   
        this.data = data;
    }

    @Override
    public String toString() {
   
        return data + "";
    }
}

二、栈实现综合计算器

1、思路

  1. 通过一个index 值(索引),来遍历我们的表达式
  2. 扫描到一个数字,直接入数栈
  3. 扫描到一个符号
    1. 符号栈为空,就直接入栈
    2. 有操作符,就进行比较
      1. 如果当前的操作符的优先级小于或者等于栈顶的操作符,就需要从数栈中pop出两个数,在从符号栈中pop出一个符号,进行运算,将得到结果入数栈,然后将当前的操作符入符号栈
      2. 如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈
  4. 当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号
  5. 最后在数栈只有一个数字,就是表达式的结果

2、代码实现

package work.rexhao.stack;

import java.util.Scanner;

/**
 * 个位数四则计算器
 *
 */
public class calculatorDemo {
   
    public static void main(String[] args) {
   
        System.out.println("请输入表达式");
        Scanner sc = new Scanner(System.in);
        String s = sc.nextLine();
        sc.close();
        System.out.println("表达式值为:" + calculator.calculate(s));
    }

}

class calculator {
   

    /**
     * 计算
     * 
     * @param s 表达式
     * @return 结果
     */
    public static int calculate(String s) {
   
        /*
         * 初始化栈
         */
        numStact ns = new numStact(20); // 数栈
        signStact ss = new signStact(20); // 运算符栈

        /*
         * 扫描字符串
         */
        int slen = s.length();
        // int index = 0; // 扫描索引
        for (int i = 0; i < slen; i++) {
   
            char c = s.charAt(i);
            if (c == '*' || c == '/') {
   
                ss.push(c);
            } else if (c == '+' || c == '-') {
   
                while (true) {
   
                    // 栈空:入栈
                    if (ss.isEmpty()) {
   
                        break;
                    }
                    // 栈非空:比较
                    if (ss.top() == '+' || ss.top() == '-') {
   
                        // 同优先级
                        break;
                    } else {
   
                        // 大于优先级
                        // 1.取出两个数和运算符
                        int ans = count(ns.pop(), ns.pop(), ss.pop());
                        // 2.结果入栈
                        ns.push(ans);
                    }
                }
                ss.push(c);
            } else {
   
                ns.push(c - '0');
            }
        }
        while(ns.top != 0) {
   
            ns.push(count(ns.pop(), ns.pop(), ss.pop()));
        }
        return ns.pop();
    }

    /**
     * 辅助计算
     */
    private static int count(int a, int b, char c) {
   
        if (c == '+') {
   
            return a + b;
        } else if (c == '-') {
   
            return b - a;
        } else if (c == '*') {
   
            return a * b;
        } else if (c == '/') {
   
            return a / b;
        }
        throw new RuntimeException("NaN");
    }
}

/**
 * 数栈
 * 
 */
class numStact {
   
    int maxSize;
    int[] data;
    int top;

    /**
     * 栈的构造器
     * 
     * @param maxSize 栈的大小
     */
    numStact(int maxSize) {
   
        this.maxSize = maxSize;
        data = new int[maxSize];
        top = -1;
    }

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

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

    /**
     * 弹出
     */
    public int pop() {
   
        if (isEmpty()) {
   
            System.out.println("栈空");
            return -1;
        }
        return data[top--];
    }

    /**
     * 压栈
     */
    public void push(int num) {
   
        if (isFull()) {
   
            System.out.println("栈满");
        } else {
   
            data[++top] = num;
        }
    }

    /**
     * 遍历
     */
    public void list() {
   
        if (isEmpty()) {
   
            System.out.println("栈空");
            return;
        }
        for (int i = 0; i <= top; i++) {
   
            System.out.printf("data[%d] = %d\n", i, data[i]);
        }
    }
}

/**
 * 符号栈
 *
 */
class signStact {
   
    int maxSize;
    char[] data;
    int top;

    /**
     * 栈的构造器
     * 
     * @param maxSize 栈的大小
     */
    signStact(int maxSize) {
   
        this.maxSize = maxSize;
        data = new char[maxSize];
        top = -1;
    }

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

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

    /**
     * 弹出
     */
    public char pop() {
   
        if (isEmpty()) {
   
            throw new RuntimeException("栈空");
        }
        return data[top--];
    }

    /**
     * 压栈
     */
    public void push(char c) {
   
        if (isFull()) {
   
            System.out.println("栈满");
        } else {
   
            data[++top] = c;
        }
    }

    /**
     * 遍历
     */
    public void list() {
   
        if (isEmpty()) {
   
            System.out.println("栈空");
            return;
        }
        for (int i = 0; i <= top; i++) {
   
            System.out.printf("data[%d] = %s\n", i, data[i]);
        }
    }

    /**
     * 栈顶元素
     */
    public char top() {
   
        return data[top];
    }
}

三、逆波兰计算器

package work.rexhao.stack;

public class polandNotationDemo {
   

    public static void main(String[] args) {
   
        System.out.println(polandNotation.calculate("34+5*6-"));
    }

}

class polandNotation {
   
    public static int calculate(String s) {
   
        numStact ns = new numStact(20);
        // 不需要符号栈
        int slen = s.length();
        for (int i = 0; i < slen; i++) {
   
            char c = s.charAt(i);
            if (c == '*' || c == '/' || c == '+' || c == '-') {
   
                ns.push(count(ns.pop(), ns.pop(), c));
            } else {
   
                ns.push(c - '0');
            }
        }
        return ns.pop();
    }

    /**
     * 辅助计算
     */
    private static int count(int a, int b, char c) {
   
        if (c == '+') {
   
            return a + b;
        } else if (c == '-') {
   
            return b - a;
        } else if (c == '*') {
   
            return a * b;
        } else if (c == '/') {
   
            return a / b;
        }
        throw new RuntimeException("NaN");
    }
}

四、中缀转后缀

1、思路

后缀表达式适合计算式进行运算,但是却不太容易写出来,尤其是表达式很长的情况下

因此在开发中,我们常常需要将中缀表达式转成后缀表达式

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

2、代码实现

/**
 * 中缀转后缀
 * 
 * @param s 中缀表达式
 * @return 后缀表达式
 */
public static String toPoland(String s) {
   
    sStact s1 = new sStact(20);// 运算符栈
    sStact s2 = new sStact(20);// 中间结果栈
    int slen = s.length();
    for (int i = 0; i < slen; i++) {
   
        char c = s.charAt(i);
        if (s1.isEmpty() || c == '(') {
   
            s1.push(c);
        } else if (c == ')') {
   
            while (true) {
   
                char temp = s1.pop();
                if (temp == '(') {
   
                    break;
                }
                s2.push(temp);
            }
        } else if (c == '+' || c == '-') {
   
            while (true) {
   
                if (s1.isEmpty() || s1.top() == '(') {
   
                    s1.push(c);
                    break;
                }
                s2.push(s1.pop());
            }
        } else if (c == '*' || c == '/') {
   
            while (true) {
   
                if (s1.isEmpty() || s1.top() == '+' || s1.top() == '-' || s1.top() == '(') {
   
                    s1.push(c);
                    break;
                }
                s2.push(s1.pop());
            }
        } else {
   
            // 数字
            s2.push(c);
        }
    }
    while (!s1.isEmpty()) {
   
        s2.push(s1.pop());
    }
    StringBuilder sb = new StringBuilder();
    while (!s2.isEmpty()) {
   
        sb.append(s2.pop());
    }
    return sb.reverse().toString();
}
目录
相关文章
|
1月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
37 1
|
29天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
55 5
|
1月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
28天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
61 1
|
1月前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
43 5
|
1月前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
1月前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
52 4
|
1月前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
50 0
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
218 9
|
2月前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
48 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器