【数据结构】队列

一,前言

队列是一种只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表。其具有先进先出的特性,入队的一端称之为队尾,出队的一端称之为队头,结构如下图所示:

二,基本实现

队列根据底层存储结构不同分为顺序结构和链式结构,本文模仿Java集合类中的实现方式,采用双链表作为基本存储结构。

1,接口定义

本次实现选择传递泛型,故接口类 也写作泛型类。

java 复制代码
public interface IQueue<T> {
    // 入队
    boolean offer(T val);
    // 出队
    T poll();
    // 获取队头元素
    T peek();
    // 获取队列中有效元素个数
    int size();
    // 检测队列是否为空
    boolean isEmpty();
}

2,实现类定义

QueueImpl实现类同样定义为泛型类并实现IQueue<T>接口,定义size变量存储队列中有效元素个数,定义Node<T>节点内部类作为基本存储结构,定义first/last引用指向队头和队尾,并提供无参构造方法对所有成员变量进行初始化赋值。

java 复制代码
public class QueueImpl<T> implements IQueue<T> {

    private int size;
    private Node<T> first;
    private Node<T> last;

    /**
     * 无参构造方法*/
    public QueueImpl() {
        this.size = 0;
        this.first = null;
        this.last = null;
    }

    /**
     * 节点内部类*/
    private static class Node<T> {
        T val;
        Node<T> next;
        Node<T> prev;

        Node(T val) {
            this.val = val;
        }
    }
}

3,主要方法实现

<1> 入队

在入队时,根据队列容量不同,会存在以下两种情况:

1,队列为空 ==> 将头尾指针同时指向新节点

2,队列不为空 ==> 尾插新节点,如下图所示:

java 复制代码
    /**
     * 入队*/
    @Override
    public boolean offer(T val) {
        // 准备待插入节点
        Node<T> node = new Node<>(val);
        // 根据容量确定插入方式
        if(isEmpty()) {
            this.first = node;
            this.last = node;
        } else {
            this.last.next = node;
            node.prev = this.last;
            this.last = node;
        }
        // 维护队列有效元素个数
        this.size ++;
        return true;
    }
<2> 出队

在出队时,根据队列容量不同,会存在以下两种情况:

1,队列为空 ==> 抛出异常

2,队列不为空 ==> 头删,如下图所示:

java 复制代码
    /**
     * 出队*/
    @Override
    public T poll() {
        // 判空
        if(isEmpty()) {
            throw new QueueEmptyException("队列为空,无法执行出队操作!");
        }
        // 根据容量确定删除方式 并 保留出队元素
        T oldVal = this.first.val;
        if(this.size == 1) {
            this.first = null;
            this.last = null;
        } else {
            this.first = this.first.next;
            this.first.prev = null;
        }
        // 维护队列有效元素个数
        this.size --;
        return oldVal;
    }
<3> 获取队头元素

在获取队头元素时,根据队列容量不同,会存在以下两种情况:

1,队列为空 ==> 抛出异常

2,队列不为空 ==> 返回头节点值

java 复制代码
    /**
     * 获取队头元素*/
    @Override
    public T peek() {
        // 判空
        if(isEmpty()) {
            throw new QueueEmptyException("队列为空,无法执行获取队头元素操作!");
        }
        return this.first.val;
    }

三,循环队列

在实际使用中,我们有时还会接触到循环队列,如操作系统中的生产者消费者模型。而循环队列通常使用数组实现,其大致逻辑结构如下图所示:

1,实现逻辑

<1> 步进

为了能够使得元素在数组中循环存储,即在数组末尾步进后重新回到数组开头,故下一步下标计算公式为:

index = (index + 1) % n; (其中n为数组长度,+1为步进长度)

如下图所示:

<2> 判空与判满

在循环队列中,若是不加任何处理,那么队列满和队列空时首尾指针都会指向同一个位置,此时就难以判断队列的存储状态,故我们可以通过以下几种方式进行处理:

  1. 通过添加size属性记录
  2. 保留一个位置用于判断
  3. 使用标记

第一种和第三种都很容易理解,第二种的处理逻辑如下:

空出一个位置不存放元素作为标记。

当首尾指针指向同一个位置时,队列为空;

当(尾指针 + 1) % 数组长度 == 头指针时,队列为满,如下图所示。

2,基础实现

由于循环队列仅在步进和判断队列状态与顺序表有所不同,其出队/入队均与顺序表尾插/头删类似,故不作额外讲解。此处引入LeetCode一道设计题,可用于练习基于数组实现循环队列。622. 设计循环队列 - 力扣(LeetCode)622. 设计循环队列 - 设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为"环形缓冲器"。循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。你的实现应该支持如下操作: * MyCircularQueue(k): 构造器,设置队列长度为 k 。 * Front: 从队首获取元素。如果队列为空,返回 -1 。 * Rear: 获取队尾元素。如果队列为空,返回 -1 。 * enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。 * deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。 * isEmpty(): 检查循环队列是否为空。 * isFull(): 检查循环队列是否已满。 示例:MyCircularQueue circularQueue = new MyCircularQueue(3); // 设置长度为 3circularQueue.enQueue(1); // 返回 truecircularQueue.enQueue(2); // 返回 truecircularQueue.enQueue(3); // 返回 truecircularQueue.enQueue(4); // 返回 false,队列已满circularQueue.Rear(); // 返回 3circularQueue.isFull(); // 返回 truecircularQueue.deQueue(); // 返回 truecircularQueue.enQueue(4); // 返回 truecircularQueue.Rear(); // 返回 4 提示: * 所有的值都在 0 至 1000 的范围内; * 操作数将在 1 至 1000 的范围内; * 请不要使用内置的队列库。https://leetcode.cn/problems/design-circular-queue/description/ 博主的解题代码如下:

java 复制代码
class MyCircularQueue {
    /**
     * 设计一个循环队列
     * 判满/判空:使用usedSize计数器
     * 步进:(i + 1)% n
     * front表示队头下标,rear表示可插入位置节点*/

    private int[] queue;
    private int size;
    private int front;
    private int rear;
    private int k;

    /**
     * 构造方法*/
    public MyCircularQueue(int k) {
        this.queue = new int[k];
        this.size = 0;
        this.front = 0;
        this.rear = 0;
        this.k = k;
    }

    /**
     * 入队*/
    public boolean enQueue(int value) {
        if(isFull()) {
            return false;
        } else {
            // -rear指向可插入位置,故先插入后步进
            this.queue[this.rear] = value;
            this.rear = (this.rear + 1) % this.k;
            this.size ++;
            return true;
        }
    }

    /**
     * 出队*/
    public boolean deQueue() {
        if(isEmpty()) {
            return false;
        } else {
            this.front = (this.front + 1) % this.k;
            this.size --;
            return true;
        }
    }

    /**
     * 获取队头元素*/
    public int Front() {
        if(isEmpty()) {
            return -1;
        } else {
            return this.queue[this.front];
        }
    }

    /**
     * 获取队尾元素*/
    public int Rear() {
        if(isEmpty()) {
            return -1;
        } else {
            if(this.rear == 0) {
                return this.queue[this.k - 1];
            } else {
                return this.queue[this.rear - 1];
            }
        }
    }
    
    /**
    * 判空*/
    public boolean isEmpty() {
        return this.size == 0;
    }

    
    /**
    * 判满*/
    public boolean isFull() {
        return this.size == this.k;
    }
}

四,双端队列 Deque

双端队列是指两端都允许进行入队和出队操作的队列,其在Java集合类中被定义为接口Deque,当使用该类型创建队列时,我们所获得的就是双端队列。创建代码如下:

java 复制代码
Deque<Integer> queue1 = new ArrayDeque<>(); // 基于可变数组的顺序存储实现
Deque<Integer> queue2 = new LinkedList<>(); // 基于双向链表的链式存储实现

Deque不仅可以作为普通队列使用,还可以作为栈使用,并且可以用于实现单调队列等算法。

相关推荐
步行cgn1 小时前
Spring 通过 factory-method 实例化 Bean 详解与常见错误排查
java·后端
lv__pf1 小时前
seata【实战 msb】
java·开发语言·后端
cxoptics2 小时前
偏振片(起偏器/检偏器)怎么区分?
java
StevenLdh2 小时前
第四期 · 云端同步与冲突仲裁:离线可写、多端一致的工程解法
java·微信小程序
Rabitebla2 小时前
【Linux系统编程】指令(三):创建、删除、复制、移动、看内容
linux·数据结构·c++·算法
事已至此先睡覺吧2 小时前
第三篇:Java 流程控制详解:条件判断、循环与跳转语句
java·开发语言
毕业设计7032 小时前
(免费领源码) SpringBoot 游戏交易平台17600-java、PHP、python、C#、小程序、大数据、单片机、网络工程等)
java·spring boot·mysql·决策树·mybatis·idea·推荐算法
一 乐2 小时前
自习室座位预约管理系统|基于springboot + vue自习室座位预约管理系统(源码+数据库+文档)
java·vue.js·spring boot
此时不提桶,更待何时3 小时前
02-02-B-AQS与JUC锁面试与生产事故实战
java