数据结构之队列的详解

news2024/11/16 23:31:06

文章目录

  • 一.什么是队列
  • 二.队列的使用
    • 2.1 队列的基本操作
    • 2.2 队列的基本使用
  • 三.队列的模拟实现
    • 3.1 数组实现队列
    • 3.2 链表实现队列
  • 四.队列的应用
    • 4.1 设计循环队列
    • 4.2 设计双端队列
    • 4.3 队列实现栈
    • 4.4 栈实现队列
  • 五.总结


一.什么是队列

  • 队列是一种先入先出(FIFO)的线性表数据结构
  • 添加和删除操作只在表的两端进行,一端为队头,另一端为队尾
  • 添加操作在队尾进行,称为入队或进队,删除操作在队头进行,称为出队

二.队列的使用

2.1 队列的基本操作

在这里插入图片描述
队列的图示
在这里插入图片描述

2.2 队列的基本使用

java内部的api

在这里插入图片描述

public static void main(String[] args) {
        Queue<Integer> q = new LinkedList<>();
        q.offer(1);
        q.offer(2);
        q.offer(3);
        q.offer(4);
        q.offer(5); // 从队尾入队列
        System.out.println(q.size());
        System.out.println(q.peek()); // 获取队头元素
        q.poll();
        System.out.println(q.poll()); // 从队头出队列,并将删除的元素返回
        if(q.isEmpty()){
            System.out.println("队列空");
        }else{
            System.out.println(q.size());
        }
    }

三.队列的模拟实现

  • 可以使用数组或链表实现队列
  • 使用数组实现时,需要维护两个指针front和rear,分别指向队头和队尾的下一个位置
  • 使用链表实现时,链表的头节点作为队头,尾节点作为队尾
  • 实现需要包含的方法有:入队add、出队remove、获取队头peek、判断是否为空isEmpty等

3.1 数组实现队列

public class ArrayQueue {
    private int front;
    private int rear;
    private int[] arr;
    private int capacity;
    
    public ArrayQueue(int capacity) {
        this.capacity = capacity;
        front = rear = 0;
        arr = new int[capacity];
    }
    
    // 入队操作,将元素加入队尾
    public void add(int elem) {
        if (rear == capacity) {
            System.out.println("队列已满");
            return;
        }
        arr[rear] = elem;
        rear++;
    }
    
    // 出队操作,移除队头元素
    public int remove() {
        if (front == rear) {
            System.out.println("队列为空"); 
            return -1;
        }
        int elem = arr[front];
        front++;
        return elem;
    }  
    
    // 获取队头元素
    public int peek() {
        if (front == rear) {
            System.out.println("队列为空");
            return -1;
        }
        return arr[front];
    }  
    
    // 判断队列是否为空
    public boolean isEmpty() {
        return front == rear;
    }
}

3.2 链表实现队列

/**
 * @Author 12629
 * @Description:
 */
public class MyQueue {
    

    static class Node {
        public int val;
        public Node next;

        public Node(int val) {
            this.val = val;
        }
    }
    public Node head;
    public Node last;
    public int usedSize;

    //入队
    public void offer(int val) {
        Node node = new Node(val);
        if(head == null) {
            head = node;
            last = node;
        }else {
            last.next = node;
            last = node;
        }
        usedSize++;
    }

    public int poll() {
        if(empty()) {
            throw new EmptyException("队列为空");
        }
        int ret = head.val;
        head = head.next;
        if(head == null) {
            last = null;//只有一个节点 那么last也要置空
        }
        usedSize--;
        return ret;
    }

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

    public int peek() {
        if(empty()) {
            throw new EmptyException("队列为空");
        }
        return head.val;
    }

    public int getUsedSize() {
        return usedSize;
    }
}

四.队列的应用

4.1 设计循环队列

其实我们在设计循环队列的时候,我们最重要的一点就是如何考虑空与满的情况
大家肯定很难理解我在说什么,大家看我接下来的操作.
在这里插入图片描述
我们只要解决上面俩个核心问题,就能完整的构造循环队列
这思路巧妙的应用了一个取模运算
在这里插入图片描述

当然这里我们提供了三种思路:

  1. 通过添加 size 属性记录
 public boolean enQueue(int value) {
        if (size == elem.length) return false; //判断满
        elem[rear] = value;
        rear = (rear + 1) % elem.length;
        size++;
        return true;
    }
    
    public boolean deQueue() {
        if (size == 0) return false; //判断空
        front = (front + 1) % elem.length;
        size--;
        return true;
    }
  1. 保留一个位置
public class MyCircularQueue {
    private int[] elem;
    private int front;
    private int rear;
    
    public MyCircularQueue(int k) {
        elem = new int[k+1];  //多一位
    }
    
    public boolean enQueue(int value) {
        if ((rear + 1) % elem.length == front) return false; //判断满
        elem[rear] = value;
        rear = (rear + 1) % elem.length;  
        return true;
    }  
}  
  1. 使用标记
  public boolean enQueue(int value) {
        if (full) return false;   //判断满
        elem[rear] = value;
        rear = (rear + 1) % elem.length;  
        if (rear == front) full = true; //修改标记
        return true;
    }
    
    public boolean deQueue() {
        if (isEmpty()) return false;  //判断空
        front = (front + 1) % elem.length;
        full = false;     //修改标记
        return true;
    } 

具体步骤:
5. 使用数组elem存储队列元素,定义front和rear指针表示队头和队尾位置。
6. enQueue(value)方法:先判断队列是否已满,未满则将元素加入rear位置,rear加1取模防止越界。
7. deQueue()方法:先判断队列是否为空,非空则front加1取模。
8. Front()方法:直接返回front位置元素,队空则返回-1。
9. Rear()方法:直接返回rear-1位置元素,队空则返回-1。需要判断rear是否为0,是则返回length-1位置元素。
10. isEmpty()方法:通过判断front和rear是否相等确定队列是否为空。
11. isFull()方法:通过判断rear+1位置是否等于front确定队列是否已满。
时间复杂度分析:

  • enQueue和deQueue方法时间复杂度O(1)。
  • 其他方法时间复杂度O(1)。
    空间复杂度分析:O(n),数组使用O(n)空间。

具体代码:

class MyCircularQueue {

    private int[] elem;
    private int front;//表示队列的头
    private int rear;//表示队列的尾

    public MyCircularQueue(int k) {
        //如果是浪费空间 这里必须处理多加一个1
        this.elem = new int[k+1];
    }

    /**
     * 入队列
     * @param value
     * @return
     */
    public boolean enQueue(int value) {
        //1、检查是否队列是满的
        if(isFull()){
            return false;
        }
        //2、
        elem[rear] = value;
        //rear++;
        rear = (rear+1) % elem.length;
        return true;
    }

    /**
     * 出队列
     * @return
     */
    public boolean deQueue() {
        if(isEmpty()) {
            return false;
        }
        //front++;
        front = (front+1) % elem.length;
        return true;
    }

    /**
     * 得到队头元素
     * @return
     */
    public int Front() {
        if(isEmpty()) {
            return -1;
        }
        return elem[front];
    }

    /**
     * 得到队尾元素
     * @return
     */
    public int Rear() {
        if(isEmpty()) {
            return -1;
        }
        int index = (rear == 0) ? elem.length-1 : rear-1;
        return elem[index];
    }
    
    public boolean isEmpty() {
        return front == rear;
    }

    /**
     * 队列是否为满
     * @return
     */
    public boolean isFull() {
       /* if( (rear+1) % elem.length == front) {
            return true;
        }
        return false;*/
        return (rear+1) % elem.length == front;
    }
}

4.2 设计双端队列

具体思路:

  1. 可以使用链表或数组实现,这里我们使用数组实现。定义数组elem存储数据,front和rear分别表示头尾指针。
  2. 添加方法:
  • addFront(val):将元素插入至队头,front减1取模,将val放入front位置。
  • addRear(val):将元素插入至队尾,rear加1取模,将val放入rear位置。
  1. 移除方法:
  • removeFront():移除队头元素,front加1取模,返回front位置元素。
  • removeRear():移除队尾元素,rear减1取模,返回rear位置元素。
  1. 获取方法:
  • getFront():返回front位置元素,队空则返回-1。
  • getRear():返回rear位置元素,队空则返回-1。
  1. 判断方法:
  • isEmpty():当front==rear时,队列为空,返回true,否则返回false。
  • isFull():当(rear+1)%len==front时,队列已满,返回true,否则返回false。len为数组长度。
  1. 扩容方法:当添加元素时判断队列已满,调用扩容方法expand将数组size*2,并把原数据复制过来。

具体代码:

public class Deque {
    private int[] elem;
    private int front;
    private int rear;
    private int len;
    
    public Deque(int capacity) {
        elem = new int[capacity];
        front = rear = 0;
        len = 0;
    }
    
    //在队头添加元素
    public void addFront(int val) {
        if (isFull()) expand();
        front = (front - 1 + elem.length) % elem.length;
        elem[front] = val;
        len++;
    }
    
    //在队尾添加元素
    public void addRear(int val) {
        if (isFull()) expand();
        elem[rear] = val;
        rear = (rear + 1) % elem.length; 
        len++;
    }  
    
    //移除队头元素
    public int removeFront() {
        if (isEmpty()) return -1;
        int ret = elem[front];
        front = (front + 1) % elem.length;
        len--;
        return ret;
    }
    
    //移除队尾元素
    public int removeRear() {
        if (isEmpty()) return -1;
        rear = (rear - 1 + elem.length) % elem.length;
        int ret = elem[rear];
        len--;
        return ret;
    }
    
    //获取队头元素
    public int getFront() {
        if (isEmpty()) return -1;
        return elem[front];
    } 
    
    //获取队尾元素
    public int getRear() {
        if (isEmpty()) return -1;
        return elem[(rear - 1 + elem.length) % elem.length];
    }  
    
    //判断队列是否为空
    public boolean isEmpty() {
        return front == rear; 
    }
    
    //判断队列是否已满
    public boolean isFull() {
        return (rear + 1) % elem.length == front; 
    }
    
    //扩容方法
    private void expand() {
        int[] newElem = new int[elem.length * 2];
        for (int i = 0; i < len; i++) {
            newElem[i] = elem[(i + front) % elem.length]; 
        }
        front = 0;
        rear = len;
        elem = newElem;
    }
}

4.3 队列实现栈

队列实现栈,在实现栈之前,我们先了解一下栈是怎么工作的,看下图

在这里插入图片描述

再看看两个队列是怎么实现栈的过程,我们用队列模拟,要记住一个核心规则

在这里插入图片描述
我演示一下入栈的规则
在这里插入图片描述
出栈的模拟演示:
在这里插入图片描述

代码如下:

import java.util.LinkedList;
import java.util.Queue;

class MyStack {

    private Queue<Integer> qu1;
    private Queue<Integer> qu2;

    public MyStack() {
        qu1 = new LinkedList<>();
        qu2 = new LinkedList<>();
    }
    
    public void push(int x) {
        if(!qu1.isEmpty()) {
            qu1.offer(x);
        } else if (!qu2.isEmpty()) {
            qu2.offer(x);
        }else {
            qu1.offer(x);
        }
    }
    
    public int pop() {
        if(empty()) {
            return -1;//两个队列都为空,意味着当前的栈为空
        }
        if(!qu1.isEmpty()) {
            int size = qu1.size();
            for (int i = 0; i < size-1; i++) {
                //for (int i = 0; i < qu1.size()-1; i++) {
                int val = qu1.poll();
                qu2.offer(val);
            }
            return qu1.poll();
        }else {
            int size = qu2.size();
            for (int i = 0; i < size-1; i++) {
                int val = qu2.poll();
                qu1.offer(val);
            }
            return qu2.poll();
        }
    }
    //peek
    public int top() {
        if(empty()) {
            return -1;//两个队列都为空,意味着当前的栈为空
        }
        if(!qu1.isEmpty()) {
            int size = qu1.size();
            int val = -1;
            for (int i = 0; i < size; i++) {
                val = qu1.poll();
                qu2.offer(val);
            }
            return val;
        }else {
            int size = qu2.size();
            int val = -1;
            for (int i = 0; i < size; i++) {
                val = qu2.poll();
                qu1.offer(val);
            }
            return val;
        }
    }
    
    public boolean empty() {
        return qu1.isEmpty() && qu2.isEmpty();
    }
}

4.4 栈实现队列

栈实现队列,还是老样子,我们还是来看看队列的工作状态
在这里插入图片描述
具体我们使用俩个栈模拟出队的操作

在这里插入图片描述

具体代码:

import java.util.Stack;

class MyQueue {

    private Stack<Integer> stack1;
    private Stack<Integer> stack2;

    public MyQueue() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }
    
    public void push(int x) {
        stack1.push(x);
    }
    
    public int pop() {
        if(empty()) {
            return -1;
        }
        if(stack2.empty()) {
            while (!stack1.empty()) {
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }
    
    public int peek() {
        if(empty()) {
            return -1;
        }
        if(stack2.empty()) {
            while (!stack1.empty()) {
                stack2.push(stack1.pop());
            }
        }
        return stack2.peek();
    }
    
    public boolean empty() {
        return stack1.empty() && stack2.empty();
    }
}

五.总结

  • 队列是一种先入先出的线性表数据结构
  • 可以使用数组或链表实现队列,实现需要包含的方法有入队add、出队remove等
  • 队列操作的时间复杂度均为O(1),不受队列大小影响

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

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

相关文章

点评项目导入

文章目录 开篇导读项目地址导入SQL项目架构介绍后端项目导入前端项目导入 开篇导读 实战篇我们要学习以下内容 短信登录 这一块我们会使用redis共享session来实现 商户查询缓存 通过本章节&#xff0c;我们会理解缓存击穿&#xff0c;缓存穿透&#xff0c;缓存雪崩等问题&…

力扣刷题Day12

239. 滑动窗口最大值 (此题逻辑真心牛皮&#xff09; 做此题之前&#xff0c;首先明确此题的目的。我最开始没搞明白此题目的&#xff0c;看代码的时候卡死。 copy他人代码&#xff1a; from collections import dequeclass MyQueue: #单调队列&#xff08;从大到小def __ini…

6.S081——陷阱部分(一文读懂Xv6系统调用)——xv6源码完全解析系列(5)

0.briefly speaking 这篇博客将要开始尝试阅读和研究与Xv6陷阱机制相关的代码&#xff0c;主要有以下文件&#xff0c;最重要的是结合Xv6 book将Xv6处理陷阱的相关逻辑和流程弄透。在Xv6的语境中所谓陷阱的触发有以下三种情况&#xff1a; 系统调用严重错误&#xff08;比如除…

windows守护进程工具--nssm使用

一、nssm简介 nssm是一个服务封装程序&#xff0c;它可以将普通exe程序封装成服务&#xff0c;实现开机自启动&#xff0c;同类型的工具还有微软自己的srvany&#xff0c;不过nssm更加简单易用&#xff0c;并且功能强大。 它的特点如下&#xff1a; 支持普通exe程序(控制台程序…

每天看一个fortran文件(7)之寻找cesm边界层高度计算代码

自从我把我的代码移到了单独的F90 中&#xff0c;从tphysac.F90中调用后。我发现有很多的变量我没办法调用了&#xff0c;特别是边界层中原先已经算好的变量&#xff0c;比如说我想调用原来模式中的pblh,但是那是在vertical diffusion.F90中计算的&#xff0c;在tphysac中根本调…

基于springcloud实现的医院信息系统

访问【WRITE-BUG数字空间】_[内附完整源码和文档] 医疗信息就诊系统&#xff0c;系统主要功能按照数据流量、流向及处理过程分为临床诊疗、药品管理、财务管理、患者管理。诊疗活动由各工作站配合完成&#xff0c;并将临床信息进行整理、处理、汇总、统计、分析等。本系统包括以…

以前以为去流量大的部门是好事,能学技术,现在才知道,流量大的部门狗都不去!晚上9点才下班,一天到晚都是监控告警!...

流量大的部门好&#xff0c;还是流量小的部门好&#xff1f; 一位网友说&#xff0c;工作以前以为去流量大的部门是好事&#xff0c;能学技术&#xff1b;工作后才知道&#xff0c;流量大的部门和组&#xff0c;狗都不去&#xff01;待在流量大的组&#xff0c;晚上9点起步才下…

p71 内网安全-域横向网络传输应用层隧道技术

数据来源 必备知识点&#xff1a; 1、代理和隧道技术区别? 代理&#xff1a;只是解决网络的访问问题&#xff08;如&#xff1a;有些内网访问不到&#xff0c;可以用代理实现&#xff09; 隧道&#xff1a;隧道不仅是解决网络的通信问题&#xff0c;更大的作用是绕过过滤&…

菜刀、蚁剑以及冰蝎三款Webshell管理工具简介

今天继续给大家介绍渗透测试相关知识&#xff0c;本文主要内容是菜刀、蚁剑以及冰蝎三款Webshell管理工具简介。 免责声明&#xff1a; 本文所介绍的内容仅做学习交流使用&#xff0c;严禁利用文中技术进行非法行为&#xff0c;否则造成一切严重后果自负&#xff01; 再次强调&…

YOLO(你只需看一眼)技术通讲(基于论文与开源代码)

YOLO通讲 前言YOLO v1论文及项目地址介绍模型架构统一检测边界框的置信度类置信度 网络设计训练 模型局限总结 YOLO v2模型改进正则化批处理使用高分辨率分类器卷积化锚盒维度聚类 前言 YOLO作为现在目标检测技术中较为基础且流行的技术之一。本文将以开源者的论文与模型为基础…

UE5实现物体高亮描边效果(含UE相关源码浅析)

文章目录 1.实现目标2.实现过程2.1 UE Editor中相关源码2.2 深度值描边2.3 半透明材质处理2.4 遮挡处理2.5 视口边缘处理3.参考资料1.实现目标 在UE5中实现物体边缘高亮效果,且在被遮挡时在边缘显示不同的颜色,当到达视口边缘时,也会将该物体与视口边缘相交的部分高亮。 2.…

纯比例控制为什么会存在稳态误差,用纯增益系统举例

warning: 本文仅为个人思考&#xff0c;非常不严谨甚至可能会出现严重错误&#xff0c;请读者仔细甄别&#xff0c;若本文真的存在严重错误&#xff0c;恳请评论区纠正&#xff0c;我看到将会考虑修改或者删除文章 纯比例控制存在稳态误差是由其本质&#xff08;控制逻辑&#…

从FPGA说起的深度学习(九)- 优化最终章

这是新的系列教程&#xff0c;在本教程中&#xff0c;我们将介绍使用 FPGA 实现深度学习的技术&#xff0c;深度学习是近年来人工智能领域的热门话题。 在本教程中&#xff0c;旨在加深对深度学习和 FPGA 的理解。 用 C/C 编写深度学习推理代码高级综合 (HLS) 将 C/C 代码转换为…

【在线OJ项目】核心技术之用户提交代码的编译运行

目录 一、认识Java进程编程 二、在线OJ核心思路 三、封装进程的执行 四、封装文件读写 五、封装用户提交代码的编译运行 一、认识Java进程编程 在之前的文章里提到了Java进程编程的相关API【JavaEE】Java中进程编程_1373i的博客-CSDN博客https://blog.csdn.net/qq_6190341…

【代码随想录】刷题Day17

1.AVLTree判断 110. 平衡二叉树 后序遍历的强化理解&#xff1a; 所谓后续遍历&#xff0c;不仅仅是一种遍历&#xff0c;其实它是完成了所有左右子树的递归。后续遍历能将自己所求的值返回给上层节点。这在比较中很关键&#xff0c;举个例子&#xff0c;我们能得到下边节点返…

Makefile教程(Makefile的结构)

文章目录 前言一、Makefile的结构二、深入案例三、Makefile中的一些技巧总结 前言 一、Makefile的结构 Makefile 通常由一系列规则组成&#xff0c;每条规则定义了如何从源文件生成目标文件。每个规则又由目标、依赖和命令三部分组成。 下面是 Makefile 规则的基本结构&…

Matlab官方的两个配色colormap补充包

目录 一、othercolor 1、使用方法 2、图示 二、slanCM 1、使用方法 2、图示 三、从matlab上下的函数如何使用 一、othercolor 下载地址&#xff1a;matlab_othercolor.zip 1、使用方法 不指定获取颜色个数会默认256色&#xff0c;举例获取[163]号彩虹色(rainbow)&…

Java阶段二Day15

Java阶段二Day15 文章目录 Java阶段二Day15复习前日知识点对象数据类型注入数组类型注入集合类型的注入p命名空间引入外部属性文件 基于XML管理beanbean的作用域bean的生命周期代码演示生命周期后置处理器处理展示基于XML的自动装配 基于注解管理bean开启组件扫描使用注解定义B…

【A200】 TX1核心 JetPack4.6.2版本如何修改DTB文件测试全部SPI

大家好&#xff0c;我是虎哥&#xff0c;很长时间没有发布新内容&#xff0c;主要是这段时间集中精力&#xff0c;研究DTB设备树的修改&#xff0c;以适配不同载板&#xff0c;同时也是专门做了一个TX1&TX2核心&#xff0c;双网口&#xff0c;可以使用SPI 扩展CAN接口的载板…

java获取resources路径的方法

我们在写程序的时候&#xff0c;有时候会发现代码不能正常运行&#xff0c;出现提示异常的问题&#xff0c;这就说明我们的代码没有执行完&#xff0c;也就是没有 resource&#xff0c;其实遇到这种情况&#xff0c;我们只需要把代码重新执行一遍即可。 在 java中是可以实现 re…