【数据结构与算法】7、队列(Queue)的实现【用栈实现队列】

news2024/12/28 18:29:26

目录

  • 一、队列介绍
  • 二、使用 LinkedList 实现队列
  • 三、LeetCode:用【栈】实现队列
    • (1) 老师讲之前我自己的实现(Correct)
    • (2) 实现思路
    • (3) 代码实现
  • 四、jdk 的 Queue
  • 五、双端队列(Deque)
  • 六、循环队列
    • (1) 分析
    • (2) 入队
    • (3) 出队
    • (4) 动态扩容
      • ① 我自己的垃圾实现
      • ② 老师的代码实现
    • (5) 索引映射封装
    • (6) 循环队列完整代码
  • 七、循环双端队列

一、队列介绍

☘️ 队列(Queue)是一种特殊的线性表只能在头尾两端进行操作
🎁 队尾(rear):只能从队尾添加元素,一般叫做 enQueue入队
🎁 队头(front):只能从队头移除元素,一般叫做 deQueue出队
🎁 先进先出的原则,First In First Out,FIFO

在这里插入图片描述

在这里插入图片描述

二、使用 LinkedList 实现队列

在这里插入图片描述

/**
 * 利用动态数组实现栈
 */
public class Queue<E> {
    // 通过【组合】的方式使用 LinkedList
    private final List<E> list = new LinkedList<>();

    /**
     * 获取队列中的元素个数
     */
    public int size() {
        return list.size();
    }

    /**
     * 判断队列中是否一个元素都没有(判断是否是一个空队列)
     */
    public boolean isEmpty() {
        return list.isEmpty();
    }

    /**
     * 往队列中添加元素(入队)
     */
    public void enQueue(E element) {
        list.add(0, element);
    }

    /**
     * 取出队首元素, 并删之(出队)
     *
     * @return 队首元素
     */
    public E deQueue() {
        return list.remove(size() - 1);
    }

    /**
     * 获取队列的头元素
     */
    public E front() {
        return list.get(size() - 1);
    }

    /**
     * 清空队列
     */
    public void clear() {
        list.clear();
    }

}

☘️ 队列内部的实现可以直接利用动态数组和链表
☘️ 优先使用双向链表。① 队列主要是往头尾两端操作元素;② 双向链表由于有头指针 first 和尾指针 last 的存在,在头尾进行元素操作都是 O(1) 的复杂度

三、LeetCode:用【栈】实现队列

https://leetcode.cn/problems/implement-queue-using-stacks/

(1) 老师讲之前我自己的实现(Correct)

🍃 代码是正确的,通过了 LeetCode 的检查

public class MyQueue {
    private Stack<Integer> stackLeft = new Stack<>(); // 左栈
    private Stack<Integer> stackRight = new Stack<>(); // 右栈

    /**
     * 构造方法
     */
    public MyQueue() {

    }

    /**
     * 入队
     */
    public void push(int x) {
        stackLeft.push(x);
    }

    /**
     * 出队
     */
    public int pop() {
        // 先把【左栈】的元素全部出栈并入栈到【右栈】中
        while (!stackLeft.isEmpty()) {
            stackRight.push(stackLeft.pop());
        }

        Integer elem = stackRight.pop();

        // 把【右栈】元素放入【左栈】
        while (!stackRight.isEmpty()) {
            stackLeft.push(stackRight.pop());
        }

        return elem;
    }

    /**
     * 返回队首元素
     */
    public int peek() {
        // 先把【左栈】的元素全部出栈并入栈到【右栈】中
        while (!stackLeft.isEmpty()) {
            stackRight.push(stackLeft.pop());
        }

        Integer elem = stackRight.peek();

        // 把【右栈】元素放入【左栈】
        while (!stackRight.isEmpty()) {
            stackLeft.push(stackRight.pop());
        }

        return elem;
    }

    public boolean empty() {
        return stackLeft.isEmpty();
    }

}

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue obj = new MyQueue();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.peek();
 * boolean param_4 = obj.empty();
 */

(2) 实现思路

📖 准备 2 个栈:inStackoutStack
📘入队时,把元素 pushinStack
📘出队
✏️如果 outStack 为空,① 将 inStack 所有元素逐一弹出,pushoutStack;② outStack 弹出栈顶元素
✏️如果 outStack 不为空, outStack 弹出栈顶元素

在这里插入图片描述

(3) 代码实现

public class MyQueue {
    // 入队时, 往 inStack 插入元素
    private Stack<Integer> inStack = new Stack<>();
    private Stack<Integer> outStack = new Stack<>();

    /**
     * 构造方法
     */
    public MyQueue() {

    }

    /**
     * 入队(只要是入队, 就往 inStack 插入元素)
     */
    public void push(int x) {
        inStack.push(x);
    }

    /**
     * 出队
     */
    public int pop() {
        if (outStack.isEmpty()) { // 如果 outStack 为空
            // 将 inStack 所有元素逐一弹出, push 到 outStack 中
            while (!inStack.isEmpty()) {
                outStack.push(inStack.pop());
            }
        }

        return outStack.pop();
    }

    /**
     * 返回队首元素
     */
    public int peek() {
        if (outStack.isEmpty()) { // 如果 outStack 为空
            // 将 inStack 所有元素逐一弹出, push 到 outStack 中
            while (!inStack.isEmpty()) {
                outStack.push(inStack.pop());
            }
        }

        return outStack.peek();
    }

    public boolean empty() {
        return inStack.isEmpty() && outStack.isEmpty();
    }

}

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue obj = new MyQueue();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.peek();
 * boolean param_4 = obj.empty();
 */

四、jdk 的 Queue

☘️ offer():入队
☘️ poll():出队
☘️ peek():返回队头元素

☘️ jdk 的 Queue 底层也是 LinkedList

五、双端队列(Deque)

💴 双端队列是能在头尾两端添加、删除的队列
💴 英文 dequedouble ended queue 的简称
在这里插入图片描述

☆ 队尾是链表的末尾
☆ 队头是链表的第一个元素

/**
 * 双端队列
 */
public class Deque<E> {
    private final List<E> linkedList;

    public Deque() {
        this.linkedList = new LinkedList<>();
    }

    public int size() {
        return linkedList.size();
    }

    public boolean isEmpty() {
        return linkedList.isEmpty();
    }

    public void clear() {
        linkedList.clear();
    }

    /**
     * 从队尾入队
     */
    public void enQueueRear(E element) {
        linkedList.add(element);
    }

    /**
     * 从队头入队
     */
    public void enQueueFront(E element) {
        linkedList.add(0, element);
    }

    /**
     * 从队头出队
     */
    public E deQueueFront() {
        return linkedList.remove(0);
    }

    /**
     * 从队尾出队
     */
    public E deQueueRear() {
        return linkedList.remove(size() - 1);
    }

    /**
     * 获取队列的头元素
     */
    public E front() {
        return linkedList.get(0);
    }

    /**
     * 获取队列的【尾】元素
     */
    public E rear() {
        return linkedList.get(size() - 1);
    }

}

六、循环队列

(1) 分析

在这里插入图片描述

☘️ 队列底层也可以使用动态数组实现,并且各项接口也可以优化到 O(1) 的时间复杂度
☘️ 用数组实现并且优化之后的队列也叫做:循环队列(Circle Queue)

☘️ 循环双端队列:可以进行两端添加删除操作的循环队列

(2) 入队

在这里插入图片描述

在这里插入图片描述

  /**
   * 往队列中添加元素(入队)
   */
  public void enQueue(E element) {
      elements[(front + size) % elements.length] = element;
      size++;
  }

在这里插入图片描述

(3) 出队

在这里插入图片描述

  /**
   * 取出队首元素, 并删之(出队)
   *
   * @return 队首元素
   */
  public E deQueue() {
      E frontElem = elements[front];
      elements[front] = null;
      front = (front + 1) % elements.length;
      size--;
      return frontElem;
  }

(4) 动态扩容

在这里插入图片描述

① 我自己的垃圾实现

🍀 也是能够满足要求的,但代码效率不行 😪

    private void ensureCapacity(int capacity) {
        int oldCapacity = elements.length;
        // 如果所需容量足够, 则不扩容
        if (oldCapacity >= capacity) return;

        // 申请全新的数组空间(新容量是旧容量的 1.5 倍)
        capacity = oldCapacity + (oldCapacity >> 1);
        E[] newElements = (E[]) new Object[capacity];

        // 把旧数组中的数据复制到新数组中
        int index = 0;
        int flagSize = 0;
        E[] oldElements = this.elements;
        while (!this.isEmpty()) {
            E e = this.deQueue();
            flagSize++;
            newElements[index] = e;
            index++;
        }

        // elements 指针指向新数组
        this.elements = newElements;
        // 队头 front 指向 0
        front = 0;
        size = flagSize;

        System.out.println(oldCapacity + "扩容为" + capacity);
    }

② 老师的代码实现

 private void ensureCapacity(int capacity) {
     int oldCapacity = elements.length;
     // 如果所需容量足够, 则不扩容
     if (oldCapacity >= capacity) return;

     // 申请全新的数组空间(新容量是旧容量的 1.5 倍)
     capacity = oldCapacity + (oldCapacity >> 1);
     E[] newElements = (E[]) new Object[capacity];

     for (int i = 0; i < size; i++) {
         newElements[i] = elements[(i + front) % elements.length];
     }

     // elements 指针指向新数组
     this.elements = newElements;
     // 队头 front 指向 0
     front = 0;

     System.out.println(oldCapacity + "扩容为" + capacity);
 }

(5) 索引映射封装

  public int realIndex(int index) {
      return (front + index) % elements.length;
  }

(6) 循环队列完整代码

/**
 * 循环队列
 */
@SuppressWarnings("all")
public class CircleQueue<E> {

    private int front; // 记录着队头元素的下标
    private int size;
    private E[] elements;

    public static final int DEFAULT_CAPACITY = 10;

    public CircleQueue(int capacity) {
        capacity = (capacity > DEFAULT_CAPACITY) ? capacity : DEFAULT_CAPACITY;
        elements = (E[]) new Object[capacity];
    }

    public CircleQueue() {
        this(DEFAULT_CAPACITY);
    }

    /**
     * 获取队列中的元素个数
     */
    public int size() {
        return size;
    }

    /**
     * 判断队列中是否一个元素都没有(判断是否是一个空队列)
     */
    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * 获取队列的头元素
     */
    public E front() {
        return elements[front];
    }

    /**
     * 往队列中添加元素(入队)
     */
    public void enQueue(E element) {
        // 扩容检测
        ensureCapacity(size + 1);

        elements[realIndex(size)] = element;
        size++;
    }

    /**
     * 我自己的垃圾实现
     */
//    private void ensureCapacity(int capacity) {
//        int oldCapacity = elements.length;
//        // 如果所需容量足够, 则不扩容
//        if (oldCapacity >= capacity) return;
//
//        // 申请全新的数组空间(新容量是旧容量的 1.5 倍)
//        capacity = oldCapacity + (oldCapacity >> 1);
//        E[] newElements = (E[]) new Object[capacity];
//
//        // 把旧数组中的数据复制到新数组中
//        int index = 0;
//        int flagSize = 0;
//        E[] oldElements = this.elements;
//        while (!this.isEmpty()) {
//            E e = this.deQueue();
//            flagSize++;
//            newElements[index] = e;
//            index++;
//        }
//
//        // elements 指针指向新数组
//        this.elements = newElements;
//        // 队头 front 指向 0
//        front = 0;
//        size = flagSize;
//
//        System.out.println(oldCapacity + "扩容为: " + capacity);
//    }
    private void ensureCapacity(int capacity) {
        int oldCapacity = elements.length;
        // 如果所需容量足够, 则不扩容
        if (oldCapacity >= capacity) return;

        // 申请全新的数组空间(新容量是旧容量的 1.5 倍)
        capacity = oldCapacity + (oldCapacity >> 1);
        E[] newElements = (E[]) new Object[capacity];

        for (int i = 0; i < size; i++) {
            newElements[i] = elements[realIndex(i)];
        }

        // elements 指针指向新数组
        this.elements = newElements;
        // 队头 front 指向 0
        front = 0;

        System.out.println(oldCapacity + "扩容为" + capacity);
    }

    private void print(String s, E[] es) {
        System.out.println("s = " + s);
        for (E e : es) {
            System.out.println(e);
        }
        System.out.println("s = " + s);
    }


    /**
     * 取出队首元素, 并删之(出队)
     *
     * @return 队首元素
     */
    public E deQueue() {
        E frontElem = elements[front];
        elements[front] = null;
        front = realIndex(1);
        size--;
        return frontElem;
    }


    /**
     * 清空队列
     */
    public void clear() {
        for (E element : elements) {
            element = null;
        }

        size = 0;
        front = 0;
    }

    public int realIndex(int index) {
        return (front + index) % elements.length;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("{size=").append(size).
            append(", capacity=").append(elements.length).
            append(", [");

        for (int i = 0; i < elements.length; i++) {
            // 不是第 0 个元素就拼接【, 】
            if (i != 0) {
                sb.append(", ");
            }

            sb.append(elements[i]);
        }

        sb.append("]}");

        return sb.toString();
    }
}

七、循环双端队列

☘️ 用数组实现并且优化之后的队列也叫做:循环队列(Circle Queue)

🍀 循环双端队列不需要存在一个叫做 rear 的变量来存储队尾元素的下标
🍃 队尾元素的下标: front + size - 1

略…

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

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

相关文章

Linux--运行指令的本质

本质&#xff1a; ①找到它 which的作用就是找到它 ②运行它 示例&#xff1a; ①告诉系统要运行的指令&#xff0c;然后系统去查找它的路径并运行它 ②自己告诉系统自己要运行的路径&#xff0c;然后系统运行它 注意&#xff1a;a.out不能运行&#xff0c;而./a.out能运行…

MES是如何帮助企业提高生产效率的

大多数提高制造生产效率的系统都是从详细分析公司的制造流程和运营开始的。这样做的目的是是为了消除浪费的不增值的流程&#xff0c;将有价值的流程系统化&#xff0c;实现生产自动化并增强增值操作。 在自动化流程方面&#xff0c;实施制造执行系统&#xff08;MES&#xff…

HTML5 游戏开发实战 | 俄罗斯方块

俄罗斯方块是一款风靡全球的电视游戏机和掌上游戏机游戏&#xff0c;它曾经造成的轰动与造成的经济价值可以说是游戏史上的一件大事。这款游戏看似简单但却变化无穷&#xff0c;游戏过程仅需要玩家将不断下落的各种形状的方块移动、翻转&#xff0c;如果某一行被方块充满了&…

发送邮箱验证码【spring boot】

⭐前言⭐ ※※※大家好&#xff01;我是同学〖森〗&#xff0c;一名计算机爱好者&#xff0c;今天让我们进入学习模式。若有错误&#xff0c;请多多指教。更多有趣的代码请移步Gitee &#x1f44d; 点赞 ⭐ 收藏 &#x1f4dd;留言 都是我创作的最大的动力&#xff01; 1. 思维…

Redis6之穿透、击穿、雪崩

大量的高并发的请求打在Redis上&#xff0c;但是发现Redis中并没有请求的数据&#xff0c;redis的命令率降低&#xff0c;所以这些请求就只能直接打在DB&#xff08;数据库服务器&#xff09;上&#xff0c;在大量的高并发的请求下就会导致DB直接卡死、宕机。 缓存穿透 当客户端…

一例Phorpiex僵尸网络样本分析

本文主要分析Phorpiex僵尸网络的一个变种&#xff0c;该样本通常NSIS打包&#xff0c;能够检测虚拟机和沙箱。病毒本体伪装为一个文件夹&#xff0c;通过U盘来传播&#xff0c;会隐藏系统中各盘符根目录下的文件夹&#xff0c;创建同名的lnk文件&#xff0c;诱导用户点击。 病…

TF卡/U盘系统备份

Jetson nano使用TF卡/U盘来装载系统&#xff0c;如果TF卡/U盘丢失或者损坏&#xff0c;那么Jetson nano上的数据都会丢失&#xff0c;所以一定要备份好TF卡/U盘。这篇文章可以帮你备份你的Jetson nano系统。主要内容为备份TF卡/U盘&#xff0c;制作Jetson nano系统镜像以及在需…

Java——《面试题——ElasticSearch篇》

目录 1、谈谈分词与倒排索引的原理 2、说说分段存储的思想 3、谈谈你对段合并的策略思想的认识 4、了解文本相似度 TF-IDF吗 5、能说说ElasticSearch 写索引的逻辑吗&#xff1f; 6、熟悉ElasticSearch 集群中搜索数据的过程吗&#xff1f; 7、了解ElasticSearch 深翻页的…

vsCode 创建新java项目(创建一个新java项目测试方法)

VSCODE环境配置-java之项目建立&#xff0c;非常适合刚准备使用VSCODE开发JAVA的人_vscode创建java项目_rainmenzhao的博客-CSDN博客 1 按 ctrl shift p 2 输入 create Java project 3 选 no build tools 4 选择 一个文件夹 存放 之后要 新建的文件 5 选完之后 自动返回主页…

获得Jolt 大奖的《持续交付》作者David Farley又一新作《现代软件工程》

戴维 法利 (David Farley) 是持续交付的先驱、思想领袖&#xff0c; 也是持续交付、 DevOps、 测试驱动开发和软件开发领域的专家。 从现代计算的早期开始&#xff0c;戴维曾担任过程序员、软件工程师、系统架构师和成功团 队的领导者&#xff0c;他掌握了计算机和软件开发的基…

ES基本操作(postman篇)

关系型数据库 -> Databases(库) -> Tables(表) -> Rows(行) -> Columns(列)。Elasticsearch -> Indeces(索引) -> Types(类型) -> Documents(文档) -> Fields(属性)。 需要注意的是&#xff1a;type的概念在es7.0之后已经删除了。 以下仅做刚入门学习…

矩阵对角线元素的和:揭秘数学之美,解密矩阵的隐秘密码

本篇博客会讲解力扣“1572. 矩阵对角线元素的和”的解题思路&#xff0c;这是题目链接。 本题的思路是&#xff1a;主对角线的下标满足i j&#xff0c;副对角线的下标满足i j size - 1&#xff0c;故只需要使用i遍历每一行&#xff0c;把(i, i)和(i, size - i - 1)的元素求和…

探索 Spring Boot 项目全过程

文章目录 &#x1f387;前言1.Spring Boot 所需环境2.Spring Boot 项目创建2.1 安装插件2.2 创建新项目2.3 项目属性配置2.4添加依赖2.4 修改项目名称2.5 添加框架支持2.6 目录介绍 3.判断Spring Boot 创建项目是否成功&#x1f386;总结 &#x1f387;前言 在 Java 这个圈子&…

1. Except

文章目录 Except前言Except 语法Except 使用举例结合其他关键字使用EXCEPT with BETWEEN operatorExcept with IN operatorEXCEPT with LIKE operator 文章参考 Except 前言 SQL中&#xff0c;EXCEPT 操作用于检索存在于第一个表中的唯一记录&#xff0c;而不是两个表中共有的…

python3GUI--网速内存小工具By:PyQt5(附源码)

文章目录 一&#xff0e;前言二&#xff0e;预览1.主界面2.动图演示3.内存详细信息查看4.自定义界面 三&#xff0e;源代码1.tool_god_GUI.py2.tool_god_ui.py3.engine.py4.CWidgets.py 四&#xff0e;总结五&#xff0e;参考 一&#xff0e;前言 本次使用PyQt5进行开发一款网…

(并查集) 685. 冗余连接 II ——【Leetcode每日一题】

并查集基础 并查集&#xff08;Union-find Sets&#xff09;是一种非常精巧而实用的数据结构&#xff0c;它主要用于处理一些不相交集合的合并问题。一些常见的用途有求连通子图、求最小生成树的Kruskal算法和求最近公共祖先&#xff08;LCA&#xff09;等。 并查集的基本操作主…

MySQL 分库分表实战之ShardingSpare

文章目录 概要一、安装二、配置2.1、 通用配置2.2、 分片配置2.3、读写分离 三、演练3.1、读写分离3.2、分库分表3.3、分库分表读写分离 4、总结 概要 市面上MySQL分库分表中间件还是很多的&#xff0c;主要分为两大类&#xff1a;应用层依赖类中间件&#xff08;比如sharding…

【学习周报】

最近看过的几篇论文里&#xff0c;VALOR和InstructBLIP模型使用了cross-attention机制&#xff0c;以这两篇论文为基础着重学习cross-attention相关的代码和思路。 学习内容&#xff1a; cross-attention机制学习lstm与transformer 学习时间&#xff1a; 6.26 ~ 7.1 学习笔记…

Linux--重定向:> >> <

输出重定向&#xff1a;> a.如果目标文件不存在&#xff0c;则创建文件。反之 b.本来应该显示到显示器的内容&#xff0c;被写入到了文件中 示例&#xff1a; 追加重定向: >> 示例&#xff1a; 输入重定向&#xff1a;< 本来应该从键盘中读取的内容&#xff0c;变…

分布式锁的实现方案(免费gpt4分享)

1.1基于数据库 有两个方案&#xff1a;依赖数据库排他锁以及表主键的唯一。 依赖数据库排他锁&#xff1a; 在查询语句后面增加for update&#xff0c;数据库会在查询过程中给数据库表增加排他锁 (注意&#xff1a; InnoDB 引擎在加锁的时候&#xff0c;只有通过索引进行检索…