数据结构之栈和队列的应用

news/2024/9/18 3:22:33 标签: 数据结构, 算法

目录

一、栈的应用

1. 括号匹配

 2. 计算后缀表达式

方法一(栈)

方法二(数组模拟栈)

二、队列应用

1. 二叉树层序遍历

方法一(队列)

三、总结


一、栈的应用

1. 括号匹配

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。
class Solution {
    public boolean isValid(String s) {
        // 判断括号数是否匹配
        if (s.length() %2 == 1) {
            return false;
        }
        Stack<Character> S = new Stack();
        for (int i = 0; i < s.length(); ++i) {
            char ele = s.charAt(i);
            // 左括号
            if (containLeft(ele)) {
                S.push(ele);
            } else {
                if (S.empty()) {
                    return false;
                }
                // 右括号
                if (CompareTo(S.peek(), ele)) {
                    // 将栈顶元素弹出
                    S.pop();
                } else {
                    return false;
                }
            }
        }
        return S.empty() ? true : false;
    }

// 包含左括号
public static boolean containLeft(char ch) {
    if (ch == '{') return true;
    if (ch == '(') return true;
    if (ch == '[') return true;
    return false;
}

//  检验括号是否匹配
    public static boolean CompareTo(char forth, char latter) {
        if ((forth == '{' && latter == '}') || (forth == '(' && latter == ')') || (forth == '[' && latter == ']')) {
            return true;
        }
        return false;
    }
}   

 2. 计算后缀表达式

根据 逆波兰表示法,求该后缀表达式的计算结果。

有效的算符包括 +-*/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

说明:

  • 整数除法只保留整数部分。
  • 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

后缀表达式按照从左到右的运算规则,计算表达式需要一个栈存放操作数;假设表达式成立,依次获取每一个字符串,如果字符串是操作数,则直接入栈,如果字符串是操作符,出栈两次,首先出栈的是右操作数,后出栈的是左操作数,将运算得到的新操作数入栈。 

方法一(栈)

class Solution {
    public int evalRPN(String[] tokens) {
        Stack<Integer> S = new Stack<>();
        int i = 0;
        while (i < tokens.length) {
            String token = tokens[i++];
            if (isNumber(token)) {
                S.push(Integer.parseInt(token));
            } else {
                int num2 = S.pop();
                int num1 = S.pop();
                switch(token) {
                    case "+":
                        S.push(num1 + num2);
                        break;
                    case "-":
                        S.push(num1 - num2);
                        break;
                    case "*":
                        S.push(num1 * num2);
                        break;
                    case "/":
                        S.push(num1 / num2);
                        break;
                    default:
                }
            }
        }
        return S.pop();
    }

     public boolean isNumber(String token) {
        return !("+".equals(token) || "-".equals(token) || "*".equals(token) || "/".equals(token));
    }
}

方法二(数组模拟栈)

class Solution {
    public int evalRPN(String[] tokens) {
        int n = tokens.length;
        int[] stack = new int[(n + 1) / 2];
        int index = -1;
        for (int i = 0; i < n; i++) {
            String token = tokens[i];
            switch(token) {
                case "+":
                    index--;
                    stack[index] += stack[index + 1];
                    break;
                case "-":
                    index--;
                    stack[index] -= stack[index + 1];
                    break;
                case "*":
                    index--;
                    stack[index] *= stack[index + 1];
                    break;
                case "/":
                    index--;
                    stack[index] /= stack[index + 1];
                    break;
                default:
                    index++;
                    stack[index] = Integer.parseInt(token);
            }
        }
        return stack[index];
    }
}

二、队列应用

1. 二叉树层序遍历

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

 /** public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
public List<Integer> levelOrder(TreeNode node) {
    // 定义res存储结点的值
    List<Integer> res = new LinkedList<>();
    Queue<TreeNode> queue = new LinkedList<>();
    if (node != null) {
        queue.offer(node);
    }
    while (!queue.isEmpty()) {
        TreeNode ele = queue.poll();
        res.add(ele.val);
        // 判断结点是否有左右孩子
        if (ele.left != null) {
            queue.offer(ele.left);
        }
        if (ele.right != null) {
            queue.offer(ele.right);
        }
    }
    return res;
}

不同于题目的是,返回一个一维列表,但如果需要返回二维列表,我们不仅要实现层序遍历,还需要将同一层的结点放到一起。显然,上述的算法无法解决这个问题。


既然一个一个迭代无法满足要求,那么我们就可以考虑一层一层迭代。

我们定义一个队列,队列中存放同一层的结点。那么我们需要考虑如何迭代一层结点?

假设队列中存放了根节点root,此时队列的长度为1,那么我们迭代一次就可以让第一层所有结点出队。出队的同时,会将root的子节点存放到队列中。

此时队列的长度为2,我们迭代两次可以让第二层所有的结点出队,出队同时会让出队元素的子结点入队,且队列满足FIFO特点,因而可以保证从左至右遍历。

归纳总结可知,迭代的次数等于前一层的入队元素个数,因为出队同时要进队,因而前一层全部出队后,队列中的一定是当前层的元素,而且队列的长度一定等于当前层的结点个数,按照这个过程迭代,直至遍历完所有结点。

方法一(队列)

 /** public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList();
        if (root == null) {
            return res;
        }
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while (! queue.isEmpty()) {
            List<Integer> level = new ArrayList<>();
            int currentLevelSize = queue.size();
            for (int i = 0; i < currentLevelSize; ++i) {
                TreeNode node = queue.poll();
                level.add(node);
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
            res.add(level);
        }
        return res;
    }
}

三、总结

栈和队列的应用十分广泛,本文简单地展示了这两种数据结构的特点,然而它的魅力不止这些,相信读者一定会遇到更加有趣的算法。上述内容如果有错误的地方,希望大佬们可以指正。我一直在学习的路上,您的帮助使我收获更大!觉得对您有帮助的话,还请点赞支持!我也会不断更新文章!


http://www.niftyadmin.cn/n/5653302.html

相关文章

WXpython --- python桌面应用开发

WXPython 教程&#xff1a;从零开始构建GUI应用程序 WXPython 是一个用于Python的跨平台GUI工具包&#xff0c;它允许Python开发者创建具有本地外观和感觉的桌面应用程序。在本教程中&#xff0c;我们将从安装WXPython开始&#xff0c;逐步学习如何使用它来创建一个简单的GUI应…

树莓派3B点灯(5)-- 自写驱动(按键版)(TODO)

在树莓派上新增一个按键并通过内核模块&#xff08;ko 驱动&#xff09;来处理&#xff0c;可以通过编写一个 Linux 内核模块来实现。以下是一个简单的示例&#xff0c;展示如何编写和加载一个内核模块来处理 GPIO 按键输入。 硬件连接 假设你已经将按键连接到树莓派的 GPIO 17…

语义分割数据集|河流湖泊分割|水灾预警

江河湖泊自然水灾检测数据集&#xff0c;数据集整理不易&#xff0c;获取地址在最后&#xff0c;具体信息如下&#xff1a; 总数&#xff1a;290张 类别&#xff1a;1类 数据集大小&#xff1a;约106M 数据整理不易&#xff0c;数据集获取地址如下&#xff1a; https://…

vue-watch监听功能(侦听器)详解使用

在Vue中&#xff0c;watch侦听器允许我们观察和响应Vue实例上数据的变化。当被侦听的数据发生变化时&#xff0c;可以执行异步操作或开销较大的操作&#xff0c;这是computed属性可能不适合的场景。watch侦听器提供了更灵活的方式来处理数据变化时的副作用。 基本用法 watch选…

数据处理与统计分析篇-day01-Linux基础与环境搭建

day01-Linux基础 计算机简介 概述 电子计算机, 电脑, PC, Computer, 就是由 软件 硬件组成的 电子设备. 组成 计算机硬件 CPU(运算器, 控制器) 存储器(内存, 外存) 输入设备 输出设备 计算机软件 系统软件: 充当 用户 和 计算机硬件之间的 桥梁的. PC端: windows, Linu…

【重学 MySQL】十二、SQL 语言的规则与规范

【重学 MySQL】十二、SQL 语言的规则与规范 基本规则注释语法规则命名规则基本命名规则具体命名规范其他注意事项 数据导入指令 SQL&#xff08;Structured Query Language&#xff0c;结构化查询语言&#xff09;的规则与规范是确保SQL语句能够正确执行、提高代码可读性和可维…

分布式中间件-redis相关概念介绍

文章目录 什么是redis?示意图Redis的主要特点Redis的主要用途Redis的工作原理Redis的持久化与备份 redis 6.x新增特性多线程数据加载客户端缓存新的 RESP 3 协议支持ACL&#xff08;Access Control List&#xff09;功能新增数据类型性能改进配置文件的改进其他改进 redis数据…

CompletableFutrue默认系统线程数量

CompletableFutrue默认系统线程数量 CompletableFuture 是 Java 8 引入的一个类&#xff0c;用于表示异步计算的结果。它使用 Java 的 ForkJoinPool 作为默认的线程池来执行异步任务。ForkJoinPool 的默认线程数量是根据可用处理器数量&#xff08;Runtime.getRuntime().avail…