JavaDS —— 栈 Stack 和 队列 Queue

news2024/11/15 19:42:59

栈的概念

栈是一种先进后出的线性表,只允许在固定的一端进行插入和删除操作。
进行插入和删除操作的一端被称为栈顶,另一端被称为栈底

栈的插入操作叫做进栈/压栈/入栈
栈的删除操作叫做出栈

在这里插入图片描述
现实生活中栈的例子:
在这里插入图片描述

栈的模拟实现

下面是Java集合类给我们提供的栈的方法:

在这里插入图片描述

模拟实现顺序栈

我们通过数组来模拟实现栈。

构建数组栈

我们需要两个成员变量,一个是数组,另一个是记录当前的数据个数。

    public int[] elem;
    public int usedSize;
    public MyStack() {
        elem = new int[5];
    }

push

要考虑扩容问题

    private boolean isFull() {
        if(usedSize == elem.length) {
            return true;
        }
        return false;
    }

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

    public void push(int val) {
        if(isFull()) {
            grow();
        }
        elem[usedSize++] = val;
    }

isEmpty

判断栈是否为空:

    public boolean isEmpty() {
        return usedSize == 0;
    }

pop

设置自定义异常:

public class PopException extends RuntimeException{
    public PopException() {
        super();
    }

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

删除栈顶的同时,还会返回删除的元素

    private void checkPop() throws PopException{
        if(isEmpty()) {
            //抛异常
            throw new PopException("栈已空,无法删除!!!");
        }
    }

    public int pop() {
        try {
            checkPop();
            int ret = elem[usedSize-1];
            usedSize--;
            return ret;
        } catch (PopException e) {
            e.printStackTrace();
        }
        return -1;
    }

peek

获取栈顶元素 但是不删除

    public int peek() {
        try {
            checkPop();
            return elem[usedSize-1];
        } catch (PopException e) {
            e.printStackTrace();
        }
        return -1;
    }

链式栈

链式栈顾名思义就是利用链表来保存栈

假设使用单链表制作链式栈,建议使用头插和头删法来进行push和pop操作,peak直接把头节点的值获取即可,这样时间复杂度可以为O(1);但是如果你使用尾插和尾删就是以尾节点的位置作为栈顶,在push,pop 和 peak 的时候,时间复杂度为O(N)

假设使用双向无头循环链表,无论是选择头插头删还是尾插尾删作为push与pop,时间复杂度都是O(1)

这里就不演示链式栈的代码。

Stack 的使用

在这里插入图片描述
push 入栈;pop 出栈;peak 获取栈顶元素;empty 是否为空;size 这个获取有效元素的方法是在Vector 中的,search 找到某元素距离栈顶元素的距离多少(栈顶元素记为1,然后一直到目标元素)

栈、虚拟机栈、栈帧有什么区别呢?

栈是我们的数据结构的其中一种形式,虚拟机栈是JVM虚拟机的一块内存,栈帧是运行一个方法或者函数的时候计算机给它开辟的内存。

队列的概念

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾(Tail/Rear) 出队列:进行删除操作的一端称为队头(Head/Front)

队列类似我们生活中的排队。

在这里插入图片描述

队列的模拟实现

在这里插入图片描述
上面是Java集合类给我们提供的队列的方法,其中add和offer都是入队操作,poll 和 remove 都是出队操作,element 和 peek 都是获取队列头部的元素。

它们主要的区别是会不会抛异常~~ 下面有详细的介绍:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


链式队列

这里我们将使用数组来模拟实现队列,并且这里只实现下图所示的方法:
在这里插入图片描述

size 和 isEmpty 是队列继承的方法,并且队列没有重写这两个方法,所以上面的IDEA直接看队列的Structure 是看不到的。


假设我们使用单链表为基础,该怎么实现队列?
假设入队采用尾插,那要出队即使用头删即可
出队列使用单链表的头删即可,时间复杂度为O(1)
入队列使用尾插,一般情况下,单链表实现尾插首先要找到尾,然后才是开始插入,这时候时间复杂度就尾O(N),不是最优解,我们可以加一个尾指针保存好尾节点,这样就方便我们快速进行尾插操作。

假设入队使用头插,那出队的时候就需要使用尾删
这时候就不好弄了,即使你有尾指针在进行尾删的时候还是需要遍历链表找到尾节点的前一个结点才能完成尾删,这时候你可能会认为再定义一个指针,这就很麻烦了。

所以单链表构建队列的话,限制条件有点大,但是使用上一片文章的双向链表(无头双向循环链表 LinkedList) ,就可以随意选取一端作为入队,另一端作为出队。

这里我们入队采用尾插,出队采用头删:

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

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

    public ListNode head;
    public ListNode last;

    //入队
    public boolean offer(int data) {
        ListNode node = new ListNode(data);
        if(isEmpty()) {
            last = head = node;
        } else {
            last.next = node;
            node.prev = last;
            last = node;
        }
        return true;
    }

    public boolean isEmpty() {
        return head == null;
    }

    //出队
    public int poll() {
        if(isEmpty()) {
            return -1;
        }
        int ret = head.val;
        if(head.next != null) {
            head.next.prev = null;
        }
        head = head.next;
        return ret;
    }

    //求结点个数
    public int size() {
        ListNode cur = head;
        int count = 0;
        while(cur != null) {
            count++;
            cur = cur.next;
        }
        return count;
    }

    //获取队头元素
    public int peek() {
        if(isEmpty()) {
            return -1;
        }
        return head.val;
    }
}

这里要注意出队的代码,如果只有一个结点的情况下,你进行删除后就没有结点了,head.next.prev = null 这行代码就会发生报错。

顺序队列

顺序队列我们采用数组来实现队列,这时候我们就要思考一个问题,在不断的出队入队的情况下怎么保证空间尽可能地利用起来?
假设数组的数据已满装满的情况下,我们一直出队直到数组变空的话,怎么利用起来前面的空间?

在这里插入图片描述

循环队列

这时候我们就需要使用循环队列让队列的空间有效的利用起来。

法1 :定义一个成员变量,usedSize 记录使用了多少的空间
法2 : 定义一个标记符,记录空间是否已满
法3 :浪费一个空间,通过数学公式判断队列是否已满

在这里插入图片描述
一般情况下,我们会认为这就是队列空和满的两种状态,但是这两种状态都是 front == near,怎么办?
根据法1,我们可以记录使用了多少空间的usedSize 来判断队列是否已满,即 usedSize = 数组的长度即可认为队列已满
根据法2,我们使用标记符,首先 将标记符记为 -1,认为队列没满,当front 与near 再次相遇时,标记符乘 -1 变为1 ,判断 队列 已满,如果下一个操作时出队,那标记符再自乘 -1变回 -1 ,当front 与 rear 再次相遇时标记符自乘 -1 变为1 表示队列已满,以此类推,这里大家可以自由发挥。


第三个方法是利用队列自身来进行判断,当 rear 的下一个就是 front 的时候(即 ( rear + 1 ) % 数组长度 == front),就判断队列已满,这需要我们浪费队列的一个空间。
在这里插入图片描述


如何让rear 和 front 循环起来呢?即rear 的下标该如何制定呢?
这里有一个公式,当 rear 要 自增的时候,新的 rear = ( rear + 1 ) % 数组长度就是此时rear 对应的实际下标,当 rear 为 7 时,rear 向下移动一格时,新的 rear 就是 ( 7 + 1 ) % 8 = 0, 正好就是 7 下一个的下标值 0


问题拓展 (数组下标循环的小技巧)

下标往后移动(offset 小于 array.length): index = (index + offset) % array.length

在这里插入图片描述


下标往前移动(offset 小于 array.length): index = (index + array.length - offset) % array.length

array.length - offset 其实就是变相地让 小标变成向后 移动。
在这里插入图片描述


顺序队列的代码
public class MyQueue {
    int front;
    int rear;
    int[] elem;

    public MyQueue() {
        elem = new int[4];
    }

    //入队
    public boolean offer(int data) {
        if(isFull()) {
            return false;
        }
        elem[rear] = data;
        rear = (rear + 1) % elem.length;
        return true;
    }

    //队列是否已满
    private boolean isFull() {
        return (rear + 1) % elem.length == front;
    }

    //队列是否为空
    public boolean isEmpty() {
        return rear == front;
    }

    //出队
    public int poll() {
        if(isEmpty()) {
            return -1;
        }
        int ret = elem[front];
        front = (front + 1) % elem.length;
        return ret;
    }

    //求元素个数
    public int size() {
        int count = 0;
        for (int i = front; i < rear; i++) {
            count++;
        }
        return count;
    }

    //获取队头元素
    public int peek() {
        if(isEmpty()) {
            return -1;
        }
        return elem[front];
    }
}

Queue

在这里插入图片描述
上面是我们常用的队列方法

在这里插入图片描述
队列Queue本质上是一个接口,所以Queue 不能被实例化,那如何使用?

Deque (双端队列)

双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。
那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。

在这里插入图片描述
在这里插入图片描述
我们可以发现 Deque 其实是继承了 Queue 接口,但是还是一个接口,还是不能实例化,那怎么使用?请看下面解说。

LinkedList 和栈与队列的关系

在这里插入图片描述

在这里插入图片描述

LinkedList 有很多接口其中包括了 Deque ,而Deque 这个接口其实继承了 Queue ,也就是队列,所以我们可以实例化 一个LinkedList 对象然后通过 Queue 接收就可以使用普通队列的方法,同理,通过实例化一个LinkedList 对象然后通过 Deque 接收就可以使用双端队列的方法


    public static void main(String[] args) {
        LinkedList<Integer> linkedList = new LinkedList<>();
        linkedList.push(1);
        linkedList.push(2);
        linkedList.push(3);
        System.out.println(linkedList.peek());
        System.out.println(linkedList.pop());
        System.out.println(linkedList.peek());
    }

在这里插入图片描述

LinkedList还可以当成栈来使用,也就是说LinkedList还包含栈的方法,自身功能很强大。

小结:
LinkedList 不仅可以作为不带头的双向循环链表使用,还可以当成栈或者队列使用。


在实际工程中,使用Deque接口是比较多的,栈和队列均可以使用该接口。

Deque<Integer> stack = new ArrayDeque<>();//双端队列的线性实现
Deque<Integer> queue = new LinkedList<>();//双端队列的链式实现

习题链接:
http://t.csdnimg.cn/aV91m

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mysql和oracle数据库的备份和迁移

本文主要讲述了mysql数据库的备份和迁移&#xff1b;oracle的肯定是备份和数据迁移&#xff0c;同时介绍了linux的定时任务。 一、mysql Docker容器下mysql备份方法 1、数据库备份 # 进入容器docker exec -it 《containerId容器id》 /bin/bash# mysqldump命令备份并生成数据…

02MFC画笔/画刷/画椭圆/圆/(延时)文字

文章目录 画实心矩形自定义画布设计及使用连续画线及自定义定义变量扇形画椭圆/圆输出颜色文本定时器与定时事件 画实心矩形 自定义画布设计及使用 连续画线及自定义定义变量 扇形 画椭圆/圆 输出颜色文本 定时器与定时事件

近期matlab学习笔记,学习是一个记录,反复的过程

近期matlab学习笔记&#xff0c;学习是一个记录&#xff0c;反复的过程 matlab的mlx文件在运行的时候&#xff0c;不需要在文件夹路径下&#xff0c;也能运行&#xff0c;但是需要调用子函数时&#xff0c;就需要在文件所在路径下运行 那就先运行子函数&#xff0c;把路径换过来…

【STM32CubeMX安装教程】

【STM32CubeMX安装教程】 1. 前言2. 下载软件3. 安装配置4. 测试5. 小结 1. 前言 STM32CubeMX是一款图形化工具&#xff0c;可以非常轻松地配置STM32微控制器和微处理器&#xff0c;以及为Arm Cortex-M内核生成相应的初始化C代码&#xff0c;或为Arm Cortex-A内核生成部分Linu…

电子签章 签到 互动 打卡 创意印章 支持小程序 H5 App

电子签章 签到 互动 打卡 创意印章 支持小程序 H5 App 定制化

Mybatis-plus 集成 PostgreSQL 数据库自增序列问题记录

1.创建序列并绑定id CREATE SEQUENCE biz_factory_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;"id" int4 NOT NULL DEFAULT nextval(sys_user_seq::regclass), 2.实体设置KeySequence和TableId注解 注意IdType.INPUT 和 KeySequence(value …

docker inspect 如何提取容器的ip和端口 网络信息?

目录 通过原生Linux命令过滤找到IP 通过jq工具找到IP 使用docker -f 的过滤&#xff08;模板&#xff09; 查找端口映射信息 查看容器内部细节 docker inspect 容器ID或容器名 通过原生Linux命令过滤找到IP 通过jq工具找到IP jq 是一个轻量级且灵活的命令行工具&#xf…

通过FallbackFactory接口实现降级逻辑

触发限流或熔断后的请求不一定要直接报错&#xff0c;也可以返回一些默认数据或者友好提示&#xff0c;用户体验会更好。 给FeignClient编写失败后的降级逻辑有两种方式&#xff1a; 方式一&#xff1a;FallbackClass&#xff0c;无法对远程调用的异常做处理方式二&#xff1a…

GenAl如何改变 DevOps 中的软件测试?

TestComplete 是一款自动化UI测试工具&#xff0c;这款工具目前在全球范围内被广泛应用于进行桌面、移动和Web应用的自动化测试。 TestComplete 集成了一种精心设计的自动化引擎&#xff0c;可以自动记录和回放用户的操作&#xff0c;方便用户进行UI&#xff08;用户界面&…

读人工智能全传11人工智能会出什么错

1. 人工智能会出什么错 1.1. 一些报道是公正合理的&#xff0c;不过坦白地说&#xff0c;大部分报道都愚蠢得无可救药 1.2. 一些报道颇有知识性和引导性&#xff0c;而大部分则是杞人忧天式的恐吓 1.3. 滑稽的报道迎合了大众对人工智能的“终结者式恐惧” 1.3.1. 我们创造出…

python库(13):Tablib库简化数据处理

1 Tablib简介 数据处理是一个常见且重要的任务。无论是数据科学、机器学习&#xff0c;还是日常数据分析&#xff0c;都需要处理和管理大量的数据。然而&#xff0c;标准库中的工具有时显得不够直观和简便。这时&#xff0c;我们可以借助第三方库来简化数据处理流程。Tablib 就…

一.3.(1)结型MOSFET场效应管的结构和工作原理;

N沟道结型MOSFET的结构 看完上图&#xff0c;会发现其实本质上二极管&#xff0c;三极管&#xff0c;MOS管都是PN结&#xff0c;如果第一章一.1.&#xff08;1&#xff09;半导体基础知识_木头的最外层电子是多少个-CSDN博客 理解消化以后&#xff0c;发现底层原理都一样。 例如…

Linux进程编程(使用fork函数创建进程以及fork函数的实际引用场景)

目录 一、进程以及相关概念和指令 1.什么是进程、什么是程序、有什么区别&#xff1f; 2.如何查看系统中有哪些进程&#xff1f; 3.什么是进程标识符&#xff1f; 4.什么叫父进程&#xff0c;什么叫子进程&#xff1f; 二、进程的创建 三、fork函数的实际应用场景 总结…

[每周一更]-(第105期):SSL证书过期后引发的DNS缓存问题

问题回顾&#xff1a; ​ 上班路上收到ZeroSSL邮件通知我们清点项目的SSL证书到期了&#xff0c;到公司还是登录网址查看信息&#xff0c;一看果然是7.10也就是今天到期&#xff0c;开始看下acme.sh的定制任务为何没生效&#xff0c;一看crontab脚本&#xff0c;日志任务丢垃圾…

Java高频面试基础知识点整理13

干货分享&#xff0c;感谢您的阅读&#xff01;背景​​​​​​高频面试题基本总结回顾&#xff08;含笔试高频算法整理&#xff09; 最全文章见&#xff1a;Java高频面试基础知识点整理 &#xff08;一&#xff09;Java基础高频知识考点 针对人员&#xff1a; 1.全部人员都…

CSS实现table表格:隔行换色的效果

表格是网页中十分重要的组成元素。表格用来存储数据&#xff0c;包含标题、表头、行和单元格。在HTML语言中&#xff0c;表格标记使用符号<table>表示。定义表格光使用<table>是不够的&#xff0c;还需要定义表格中的行、列、标题等内容。推荐博文&#xff1a;《HT…

LeetCode热题100刷题13:64. 最小路径和、62. 不同路径、5. 最长回文子串、1143. 最长公共子序列

64. 最小路径和 class Solution { public:int minPathSum(vector<vector<int>>& grid) {int m grid.size();int n grid[0].size();vector<vector<int>> dp(m,vector<int>(n,INT_MAX));dp[0][0]grid[0][0];if(m1 && n1)return grid…

使用 Apache Pulsar 构建弹性可扩展的事件驱动应用

本视频来自 2024 Apache Pulsar 欧洲峰会&#xff0c;由 David Kjerrumgaard, 《Pulsar in Action》书作者给大家带来的《使用 Apache Pulsar 构建弹性可扩展的事件驱动应用》分享。 嘉宾&#xff5c;David Kjerrumgaard&#xff0c;Apache Pulsar Committer&#xff0c;《Pul…

登录/注册

目录 1.HTML 2.CSS 3.JS 4.资源 5.运行结果 6.下载链接 7.注意事项 1.HTML <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-sca…

我的世界1.21多种服务端开服教程,原版/Forge/Fabric/Paper/Mohist...,Minecraft开服教程

Minecraft&#xff08;MC&#xff09;1.21版多种服务端开服教程&#xff0c;我的世界1.21服务器搭建教程&#xff0c;MC原版/Forge/Fabric/Paper/Mohist服务端搭建教程&#xff0c;我的世界MOD/插件服开服教程。 本教程使用 Linux系统MCSManager 面板来搭建Minecraft服务器。 …