Java-数据结构-栈与队列(StackQueue)

一、栈(Stack)

① 栈的概念

栈是一种特殊的线性表,它只允许固定一端进行"插入元素"和"删除元素"的操作,这固定的一端被称作"栈顶",对应的另一端就被称做"栈底"。

📚 栈中的元素遵循后进先出的原则

什么是后进先出呢?在生活中也有很多类似的例子,比如叠放的书本,堆积的盘子,它们都是栈在生活中的体现~

📕 压栈:指栈插入元素的操作,一般叫做进栈/压栈/入栈(操作位置是栈顶)。

📕 出栈:指栈的删除操作(操作位置是栈顶)。

② 栈的使用

栈有以下六种方法

方法功能
Stack()构造一个空的栈
E push(E e)将e入栈,并返回e
E pop()将栈顶元素出栈并返回
E peek()获取栈顶元素
int size()获取栈中有效元素个数
boolean empty()判断栈是否为空

E push(E e)方法:

E pop()方法:

E peek()方法:

int size()方法:

boolean empty()方法:

③ 栈的模拟实现

栈的方法与操作与顺序表,链表等相比并不难,让我们也试着模拟实现一下~

1. 栈的基本框架

上面提到了,栈也是一种线性表,并且栈和顺序表其实是相似的,所以我们使用数组来充当栈~

接下来需要设置的东西就都和顺序表大差不差啦:

📕 创建elem数组充当栈

📕 定义usedSize代表栈内有效元素个数

📕 定义DEFAULE_SIZE为默认初始大小

📖 代码示例

public class MyStack {
    private int[] elem;
    private int usedSize;
    private static final int DEFAULT_SIZE = 10;
    public MyStack(){
        elem = new int[10];
    }
}

📚 给大家看一眼我们要模拟实现的方法都有哪些

2. display 方法

就没有什么多说的啦,遍历数组就好了。

📖 代码示例

    public void display(){
        System.out.print("[");
        for(int i = 0;i < usedSize;i++){
            System.out.print(elem[i]);
            if(i != usedSize - 1){
                System.out.print(", ");
            }
        }
        System.out.println("]");
    }
3. size 方法

返回栈有效元素个数即可。

📖 代码示例

    public int size(){
        return usedSize;
    }
4. push 方法

在实现push(入栈)方法之前,我们需要先实现两个辅助方法

📕 入栈前需要先判断栈是否已满:

    public boolean full(){
        if(usedSize == elem.length){
            return true;
        }
        return false;
    }

📕 如果栈已满,需要自动扩容:

    public void grow(){
        elem = Arrays.copyOf(elem,elem.length * 2);
    }

等对栈是否满,以及扩容完毕后,我们将数据直接入栈即可~

📖 代码示例

    public void push(int num){
        if(full()){
            grow();
        }
        elem[usedSize] = num;
        usedSize++;
    }
5. pop 方法

虽然是出栈,但是pop会删除元素,所以先实现两个其他的辅助方法

📕 删除元素前,我们需要知道栈是否为空:

    public boolean empty(){
        if(usedSize == 0){
            return true;
        }
        return false;
    }

📕 如果栈为空,那么应该抛出异常:

    public static class StackEmptyException extends RuntimeException{
        public StackEmptyException() {
        }

        public StackEmptyException(String message) {
            super(message);
        }
    }

📖 代码示例

    public int pop(){
        if(empty()){
            throw new StackEmptyException("栈为空异常!");
        }
        usedSize--;
        return elem[usedSize];
    }
6. peek 方法

和pop一样,不减少usedSize就好了

📖 代码示例

    public int peek(){
        if(empty()){
            throw new StackEmptyException("栈为空异常!");
        }
        return elem[usedSize - 1];
    }
完整代码:
import java.util.*;
public class MyStack {
    public static void main(String[] args) {
    }
    private int[] elem;
    private int usedSize;
    private static final int DEFAULT_SIZE = 10;
    public MyStack(){
        elem = new int[10];
    }
    public int size(){
        return usedSize;
    }
    public void push(int num){
        if(full()){
            grow();
        }
        elem[usedSize] = num;
        usedSize++;
    }
    public int pop(){
        if(empty()){
            throw new StackEmptyException("栈为空异常!");
        }
        usedSize--;
        return elem[usedSize];
    }
    public int peek(){
        if(empty()){
            throw new StackEmptyException("栈为空异常!");
        }
        return elem[usedSize - 1];
    }
    public void grow(){
        elem = Arrays.copyOf(elem,elem.length * 2);
    }
    public boolean full(){
        if(usedSize == elem.length){
            return true;
        }
        return false;
    }
    public boolean empty(){
        if(usedSize == 0){
            return true;
        }
        return false;
    }
    public void display(){
        System.out.print("[");
        for(int i = 0;i < usedSize;i++){
            System.out.print(elem[i]);
            if(i != usedSize - 1){
                System.out.print(", ");
            }
        }
        System.out.println("]");
    }

    public static class StackEmptyException extends RuntimeException{
        public StackEmptyException() {
        }

        public StackEmptyException(String message) {
            super(message);
        }
    }
}

④ 习题:有效的括号

学习了栈之后,正好做一道小题小试牛刀吧~这题如果在学习栈之前,单纯用字符串来解决的话,不出所料应该是相当麻烦的,因为要直接对字符串整体进行计算,就要考虑到很多种情况,比如左右括号数量,括号类型是否对应等,也没办法准确的去删除已分配好的括号,即便做出了删除的办法,但频繁的对字符串进行操作也会导致代码效率低下。

但如今学习了栈就不一样了,让我们用"栈"的方式看看这题该如何解决吧:

📚 思路分析

📕 遍历字符串,当出现的' ( ',' [ ',' { '时放入栈中。

📕 当出现' ) ',' ] ',' } '时判断:如果此时栈顶元素(上一个符号)是否为其对应的括号

📕 如果是其对应括号,则"将栈顶元素出栈,然后遍历下一个字符"(此行为代表删除这对括号)

📕 如果不是其对应括号,则直接返回false(因为如果栈顶元素不匹配,则代表此字符是多余的)

📕 最后对栈进行判断,如果栈为空,则代表所有括号匹配成功,返回true,反之则返回false

📖 代码示例

class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();
        char[] str = s.toCharArray();
        for(int i = 0;i < str.length;i++){
            char c = str[i];
            if(c == ')' || c == ']' || c == '}'){
                if(stack.empty()){
                    return false;
                }
                char n = stack.peek();
                if((n == '(' && c != ')') || (n == '[' && c != ']') || (n == '{' && c != '}')){
                    return false;
                }
                stack.pop();
            }else {
                stack.push(c);
            }
        }
        if(stack.empty()){
            return true;
        }
        return false;
    }
}

二、队列(Queue)

① 队列的概念

还记得栈的性质嘛?没错,"后进先出",而队列恰恰与栈相反,队列的性质是"先进先出",也就是"只允许在一段进行插入数据操作""在另一端进行删除数据的操作"特殊线性表

📕 队尾:进行插入操作的一端

📕 对头:进行删除操作的一端

在生活中也有很多队列的体现,就比如"银行排队取钱",就像队列一样,先排队的人先取(先走),后排队的人后取(后走)~

② 队列的使用

由图我们可以看出,Queue是一个接口,并且我们还要知道,Queue底层是使用链表实现的,所以当我们对队列进行实例化的时候,是不能使用Queue的,而是应该new一个链表~

方法功能
boolean offer(E e)元素入队列
E poll()元素出队列
peek()获取对头的元素
int size()获取队列的有效元素个数
boolean isEmpty()判断队列是否为空

boolean offer(E e) 方法

E poll() 方法

peek() 方法

int size() 方法

boolean isEmpty() 方法

③ 队列的模拟实现

大部分代码以及思路都和模拟实现双向链表是一致的,如果有需要可以前往Java-数据结构-链表(LinkedList)-双向链表-CSDN博客
进行更细致的学习,如果链表学会了那队列不在话下~

在刚刚我们知道了队列(Queue)的底层是使用链表来实现的,但是我们学习过了单向链表,也学习过双向链表,对于队列的模拟实现,我们应该使用哪种链表会更加方便呢?由于队列最主要的两种操作也就仅仅是入队和出队,那么我们便对这两点展开讨论:

📚 单向链表

📕 如果从表头实现入队,那么就要从表尾实现出队,则入队为O(1),出队为O(n)

📕 如果从表尾实现入队,那么就要从表头实现出队,则入队为O(n),出队为O(1)

📚 双向链表

📕 如果从表头实现入队,那么就要从表尾实现出队,则入队为O(1),出队为O(1)

📕 如果从表尾实现入队,那么就要从表头实现出队,则入队为O(1),出队为O(1)

所以我们应该使用双向链表来模拟实现队列~

1. 队列的基本框架

📕 由于是使用双向链表,所以框架基本一致~

public class MyQueue {
    public static class ListNode{
        private int val;
        private ListNode prev;
        private ListNode next;

        public ListNode(int val) {
            this.val = val;
        }
    }
    public ListNode front;
    public ListNode last;
    public int usedSize = 0;
}
2. offer 方法

其实本质上就是双向链表的尾插法~

📚 想要实现元素的入队并不难,我们需要注意以下几点

📕 如果该队列为空,则需要将node变成新的head

📕 如果队列不为空,应该将node插入last的后继,并且让node的前驱变成last

📕 只要有元素入队,last就要进行更新

📖 代码示例

    public void offer(int e){
        ListNode node = new ListNode(e);
        if(front == null){
            front = node;
        }else{
            last.next = node;
            node.prev = last;
        }
        last = node;
        usedSize++;
    }

3. poll 方法

其实本质上就类似于双向链表的"删掉第一个结点"

📚 有以下注意事项

📕 队列为空时,直接返回null

📕 队列中只有一个元素时直接全部置null

📕 队列中有多个元素时,

📖 代码示例

    public int poll() {
        int value = 0;
        if (front == null) {
            return -1;
        } else if (front == last) {
            last = null;
            front = null;
        } else {
            value = front.val;
            front = front.next;
            front.prev = null;
            --usedSize;
        }
        return value;
    }
4. peek 方法

获取队头元素,就是上面poll的不删除版本

📖 代码示例

    public int peek(){
        if(front == null){
            return -1;
        }
        return front.val;
    }
5. size & isEmpty 方法

📖 代码示例

    public int size(){
        return usedSize;
    }
    public boolean isEmpty(){
        return front == null;
    }
完整代码:
public class MyQueue {
    public static class ListNode {
        private int val;
        private ListNode prev;
        private ListNode next;

        public ListNode(int val) {
            this.val = val;
        }
    }

    public ListNode front;
    public ListNode last;
    public int usedSize = 0;

    public void offer(int e) {
        ListNode node = new ListNode(e);
        if (front == null) {
            front = node;
        } else {
            last.next = node;
            node.prev = last;
        }
        last = node;
        usedSize++;
    }

    public void display() {
        System.out.print("[");
        ListNode cur = front;
        while (cur != null) {
            System.out.print(cur.val);
            if (cur.next != null) {
                System.out.print(", ");
            }
            cur = cur.next;
        }
        System.out.print("]");
    }

    public int poll() {
        int value = 0;
        if (front == null) {
            return -1;
        } else if (front == last) {
            last = null;
            front = null;
        } else {
            value = front.val;
            front = front.next;
            front.prev = null;
            --usedSize;
        }
        return value;
    }
    public int peek(){
        if(front == null){
            return -1;
        }
        return front.val;
    }
    public int size(){
        return usedSize;
    }
    public boolean isEmpty(){
        return front == null;
    }
}

④ 习题:设计循环队列

📚 思路提示

循环队列是个什么东西呢?其实就是将队列的头和尾连接到一起而已,没有什么特别的~我们来看看循环队列长什么样子:

这就是我们的循环队列了,其中内部的是元素的下标,外部的是用来存储元素的空间,其实看起来就像一个循环了的数组一样~

而刚刚也看到啦,就类似于环形的数组,所以对于这个环形链表,我们可以采取使用顺序表的方式来模拟实现它:

对于这个循环队列的实现,最重要的难点在于:当正常访问到队列的末尾时,如何判断它是空队列还是满队列,以及如何让它从队尾的下一位访问到队头。

对此我们有以下解法:

① 定义一个size记录存储的个数

        size == len 就是满

        size == 0 就是空

(该方法比较简单,就不过多介绍了)

② 牺牲一个空间来判满:

(我们在此使用这种方法来解决)

📕 基本框架:

class MyCircularQueue {
    public int left;
    public int right;
    public int[] elem;

    public MyCircularQueue(int k) {
        elem = new int[k + 1];
    }
    
    public boolean enQueue(int value) {
        return false;
    }
    
    public boolean deQueue() {
        return false;
    }
    
    public int Front() {
        return -1;
    }
    
    public int Rear() {
        return -1;
    }
    
    public boolean isEmpty() {
        return false;
    }
    
    public boolean isFull() {
        return false;
    }
}

📕 isFull 方法

判断循环队列是否为空的方法,由于我们刚刚说了:我们采用的是舍弃一个空间的方法,也就是队尾 + 1 正好等于该队列的大小时,也就舍弃一个空间,算它为满了。

📖 代码示例

    public boolean isFull() {
        if((right + 1) % elem.length == left){
            return true;
        }
        return false;
    }

📕 enQueue 方法

向队列中插入一个元素,这就要用到我们刚刚完成的 isFull 方法了

⭐ 如果队列为满,直接返回false

还有,别忘了我们是舍弃一块空间的方法,所以不能直接是 right % 队列大小

而应该是 (right + 1) % 队列大小,才能够跳过一块空间~ 

📖 代码示例

    public boolean enQueue(int value) {
        if(isFull()){
            return false;
        }
        elem[right] = value;
        right = (right + 1) % elem.length;
        return true;
    }

📕 isEmpty 方法

对于判断该循环队列是否为空的方法,我们只需要判断 left 是否等于 right 就行了,因为 left 始终都是在 right 后面的,当两者相等时,也就代表正好将链表的元素都删除了。

📖 代码示例

    public boolean isEmpty() {
        return left == right;
    }

📕 Front 方法

从队头获取元素的方法,只需要额外判断一下链表是否为空即可~

📖 代码示例

    public int Front() {
        if(isEmpty()){
            return -1;
        }
        return elem[left];
    }

📕 Rear 方法

获取队尾元素的方法,这个就没那么简单了,除了需要对队列是否为空做出判断,还需要注意这个问题:

我们一般插入元素后,right就会指向下一个位置,所以如果直接取right下标的元素是行不通的,我们需要取right的前一个元素~

或许你认为只需要对right - 1就好了呀,实则不然,别忘了我们是循环队列,所以当我们队列已满的时候,right 是在 0 位置的,这时我们需要访问的 index 就不是 right - 1了 ,而是队列大小-1。

📖 代码示例

    public int Rear() {
        if(isEmpty()){
            return -1;
        }
        int index = (right == 0 ? elem.length - 1 : right - 1);
        return elem[index];
    }

📕 deQueue 方法

删除队列中一个元素,首先判断是否为空,如果为空队列直接返回false~

随后就还是一样的,删除一位,队尾再向后走一位,并且记得是(left + 1)而不是left

📖 代码示例

    public boolean deQueue() {
        if(isEmpty()){
            return false;
        }
        elem[left] = 0;
        left = (left + 1) % elem.length;
        return true;
    }

也是顺利通过了~

那么这次关于栈与队列的知识,就为大家分享到这里啦,作者能力有限,如果有讲得不清晰或者不正确的地方,还请大家在评论区多多指出,我也会虚心学习的!那我们下次再见哦

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/951873.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

带格式 pdf 翻译

支持 openAI 接口&#xff0c;国内 deepseek 接口兼容 openAI 接口&#xff0c; deepseek api 又非常便宜 https://pdf2zh.com/ https://github.com/Byaidu/PDFMathTranslate

【如何从0到1设计测试用例使用Fiddler完成弱网测试】

&#x1f308;个人主页&#xff1a;努力学编程’ ⛅个人推荐&#xff1a; c语言从初阶到进阶 JavaEE详解 数据结构 ⚡学好数据结构&#xff0c;刷题刻不容缓&#xff1a;点击一起刷题 &#x1f319;心灵鸡汤&#xff1a;总有人要赢&#xff0c;为什么不能是我呢 ⭐⭐⭐测试用…

基础项目实战——贪吃蛇(c++)

目录 前言一、 游戏总体框架二、地图绘制三、光标隐藏四、地图定义五、蛇体定义六、蛇体绘制七、蛇体移动八、频率控制九、边界检测十、游戏失败十一、蛇体转向十二、食物生成十三、食物碰撞十四、整体代码十五、结语 前言 各位小伙伴们好久不见&#xff0c;前段时间非常的忙很…

排序:插入、选择、交换、归并排序

排序 &#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性 &#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记录&#xff0c;若经过排序&#xff0c;…

Windows service运行Django项目

系统&#xff1a;Windows Service 软件&#xff1a;nssm&#xff0c;nginx 配置Django项目 1、把Django项目的静态文件整理到staticfiles文件夹中 注&#xff1a;settings中的设置 STATIC_URL /static/ STATIC_ROOT os.path.join(BASE_DIR, staticfiles/) STATICFILES_DI…

comfyui精准作图之gligen

简介 在 Stable Diffusion&#xff08;SD&#xff09;中&#xff0c;GLIGEN 是一种用于增强文本到图像生成模型可控性的技术。它通过在现有的预训练扩散模型&#xff08;如 Stable Diffusion&#xff09;基础上&#xff0c;引入额外的定位输入&#xff08;如边界框、关键点或参…

【学习资源】MBSE和工业软件

工业软件从业者&#xff0c;需要学习与应用MBSE方法论&#xff0c;解决复杂问题的有效手段。笔者做一个简单介绍。 1 什么是MBSE&#xff1f; MBSE&#xff08;Model-Based Systems Engineering&#xff0c;基于模型的系统工程&#xff09;是一种系统工程方法论&#xff0c;其…

ue5 蒙太奇,即上半身动画和下半身组合在一起,并使用。学习b站库得科技

本文核心 正常跑步动画端枪动画跑起来也端枪 正常跑步动画 端枪动画的上半身 跑起来也端枪 三步走&#xff1a; 第一步制作动画蒙太奇和插槽 第二步动画蓝图选择使用上半身动画还是全身动画&#xff0c;将上半身端枪和下半身走路结合 第三步使用动画蒙太奇 1.开始把&a…

【Docker】docker compose 安装 Redis Stack

注&#xff1a;整理不易&#xff0c;请不要吝啬你的赞和收藏。 前文 Redis Stack 什么是&#xff1f; 简单来说&#xff0c;Redis Stack 是增强版的 Redis &#xff0c;它在传统的 Redis 数据库基础上增加了一些高级功能和模块&#xff0c;以支持更多的使用场景和需求。Redis…

视频转码对画质有影响吗?视频融合平台EasyCVR支持哪些转码格式?

视频转码过程是将视频文件从一种编码格式转换为另一种格式的过程&#xff0c;这一过程在现代数字媒体中扮演着至关重要的角色。众所周知&#xff0c;视频转码不仅仅是简单的格式转换&#xff0c;它涉及多个关键参数的改变&#xff0c;例如视频编码格式、比特率、分辨率以及帧率…

vscode开启调试模式,结合Delve调试器调试golang项目详细步骤

1.前期准备 (1).在vs code中的扩展程序中搜索并安装Go扩展程序 (2).安装 Delve 调试器 go install github.com/go-delve/delve/cmd/dlvlatest (3).打开vs code的命令面板&#xff0c;输入Go: Install/Update Tools&#xff0c;并单击该命令执行&#xff0c;安装或更新Go语…

springboot和vue配置https请求

项目场景&#xff1a; 代码发布到线上使用https请求需要配置ssl证书&#xff0c;前后端都需要修改。 问题描述 如图&#xff0c;我们在调用接口时报如下错误&#xff0c;这就是未配置ssl但是用https请求产生的问题。 解决方案&#xff1a; 前端&#xff1a;在vite.config.js文…

每日学习30分轻松掌握CursorAI:Cursor基础设置与配置

Cursor基础设置与配置 一、基础设置概览 1. 设置项分类表 设置类别主要功能重要程度语言设置界面及AI交互语言配置★★★★★快捷键配置自定义操作快捷键★★★★☆外观设置主题、字体、颜色方案★★★☆☆编辑器设置缩进、换行、代码风格★★★★☆AI功能设置AI响应灵敏度、…

设计模式(观察者模式)

设计模式&#xff08;观察者模式&#xff09; 第三章 设计模式之观察者模式 观察者模式介绍 观察者模式&#xff08;Observer Design Pattern&#xff09; 也被称为发布订阅模式 。模式定义&#xff1a;在对象之间定义一个一对多的依赖&#xff0c;当一个对象状态改变的时候…

QT 下拉菜单设置参数 起始端口/结束端口/线程数量 端口扫描4

上篇文章QT实现 端口扫描暂停和继续功能 3-CSDN博客 双击 添加对话框类 界面设计 由于主体代码已经写完&#xff0c;只需要更改参数的获取即可 获取起始端口结束端口的输入 槽函数 给主界面类添加调用对话框类的功能 实现功能&#xff1a;点击菜单项可以弹出对话框窗体 增加槽…

Unity自定义编辑器:基于枚举类型动态显示属性

1.参考链接 2.应用 target并设置多选编辑 添加[CanEditMultipleObjects] using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor;[CustomEditor(typeof(LightsState))] [CanEditMultipleObjects] public class TestInspector :…

《代码随想录》Day31打卡!

《代码随想录》贪心算法&#xff1a;合并区间 本题的完整题目如下所示&#xff1a; 本题的完整思路如下所示&#xff1a; 1.本题依然是先对数组的左边界进行排序。将数组的第一个元素赋值给current。 2.遍历数组&#xff0c;判断current中的右边界和当前元素的左边界是否有重叠…

灵活运用事务回滚,快捷处理多张数据表格

各位编程宝子们&#xff08;尤其是对MySQL了解不多的宝子们&#xff09;在使用关系表处理时&#xff0c;有时候会希望简单一次性解决多张表的数据处理&#xff0c;但又有时候无从下手。其实有时候掌握数据的事务和回滚便可以简单解决这些事情&#xff0c;接下来我将以一个学生信…

Github提交Pull Request教程 Git基础扫盲(零基础易懂)

1 PR是什么&#xff1f; PR&#xff0c;全称Pull Request&#xff08;拉取请求&#xff09;&#xff0c;是一种非常重要的协作机制&#xff0c;它是 Git 和 GitHub 等代码托管平台中常见的功能&#xff0c;被广泛用于参与社区贡献&#xff0c;从而促进项目的发展。 PR的整个过…

kvm 解决 安装windows 虚拟机cpu 核数问题

通过lscpu命令查到我本机的cpu信息如下 CPU(s): 12 —— 系统的总逻辑处理单元数量&#xff08;包括所有核心和逻辑处理器&#xff09;。Thread(s) per core: 2 —— 每个物理核心支持 2 个线程&#xff08;表示启用了超线程技术&#xff09;。Core(s) per socket: 6 —— 每个…