先进先出的排队结构——队列(Queue)

设计背景

“后进先出”(LIFO)的特性相对应,应当还有一种“先进先出”(FIFO)特性的数据结构,这种数据结构被称之为队列(Queue)。该结构的实现过程与栈几乎类似,区别在于每次存入元素和获取元素的位置不同。
在这里插入图片描述


结构分析

【结构类型】线性结构
【底层实现】动态数组(ArrayList)
【核心方法】
public void enqueue(E e); //入队
public E dequeue(); //出队
public E getFront(); //获取队首的元素


代码实现

1. 利用ArrayList实现普通队列
public class ArrayQueue<E> implements Queue<E> {
    /**
     * 实例域:动态数组
     */
    private ArrayList<E> array;

    /**
     * 带参构造器:对实例域进行初始化
     * @param capacity 数组容量
     */
    public ArrayQueue(int capacity) {
        array = new ArrayList<>(capacity);
    }

    /**
     * 无参构造器:利用默认值对实例域进行初始化
     */
    public ArrayQueue() {
        array = new ArrayList<>();
    }

    @Override
    public void enqueue(E e) {
        array.addLast(e);
    }

    @Override
    public E dequeue() {
        return array.removeFirst();
    }

    @Override
    public E getFront() {
        return array.get(0);
    }

    @Override
    public boolean isEmpty() {
        return array.isEmpty();
    }

    @Override
    public int getSize() {
        return array.getSize();
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append("Queue: ");
        res.append("front [");
        for(int i = 0 ; i < array.getSize() ; i ++){
            res.append(array.get(i));
            if(i != array.getSize() - 1)
                res.append(", ");
        }
        res.append("] tail");
        return res.toString();
    }

}

改进策略

【改进思想】普通的数组队列(ArrayQueue)进行出队操作时,需要将后方的每一个元素向前移动(时间复杂度为O(n)级别),这导致普通队列的性能较低。我们期望更改队列设计,以达到出队操作不需要对后方元素进行遍历,并且使得出队操作完成后所闲置的空间能被循环利用。根据这种思想所改良的队列就称之为循环队列(LoopQueue)。
在这里插入图片描述
【改进方法】因为循环队列(LoopQueue)具有“空间再利用”的结构性,故底层无法直接使用原生的动态数组(ArrayList)实现,而需要在ArrayList的基础上进行重新改写。
设计队列的首/尾索引front和tail,初始化时预留一个空间长度,令front == tail == 0,此时队列为空
当(tail + 1) % data.length == front时,队列为满,触发扩容操作。
缩容操作的触发条件与ArrayList类似,但要注意将预留空间除外。


代码实现

2. 利用Array实现循环队列
public class LoopQueue<E> implements Queue<E> {
    /**
     * 实例域:泛型数组、首/尾索引、元素个数
     */
    private E[] data;
    private int front;
    private int tail;
    private int size;

    /**
     * 带参构造器:初始化实例域
     * @param capacity 数组容量
     */
    public LoopQueue(int capacity) {
        data = (E[]) new Object[capacity + 1];  // 注:循环队列需预存一个闲置空间
        front = 0;
        tail = 0;
        size = 0;
    }

    /**
     * 无参构造器:用默认值初始化实例域
     */
    public LoopQueue() {
        this(20);
    }

    /**
     * 方法:获取数组容量
     * @return
     */
    public int getCapacity() {
        return data.length - 1;  // 注:循环队列预存了一个闲置空间
    }

    @Override
    public void enqueue(E e) {
        // 当数组已满时进行扩容操作,新队列大小为原来的2倍
        if ((tail + 1) % data.length == front) {
            resize(getCapacity() * 2);
        }
        // 将元素插入队尾
        data[tail] = e;
        // 尾索引后移一位
        tail = (tail + 1) % data.length;
        // 元素个数加一
        size++;
    }

    @Override
    public E dequeue() {
        // 判断队列是否为空
        if (isEmpty()) {
            throw new IllegalArgumentException("Queue is empty!");
        }
        // 将队首元素存入临时变量
        E ret = data[front];
        // 移除队首元素
        data[front] = null;
        // 首索引后移一位
        front = (front + 1) % data.length;
        // 元素个数减一
        size--;
        // 当数组3/4的空间为空时,进行缩容操作,新队列大小为原来的一半
        if (size == getCapacity() / 4 && size / 2 != 0) {
            resize(getCapacity() / 2);
        }
        // 返回临时变量
        return ret;
    }

    /**
     * 方法:对队列的容量进行调整
     * @param capacity 新队列容量
     */
    private void resize(int capacity) {
        // 新建一个数组
        E[] newData = (E[]) new Object[capacity];
        // 将旧数组的数据拷贝至新数组
        for (int i = 0; i < size; i++) {
            newData[i] = data[(i + front) % data.length];
        }
        // 将数组变量引用至新数组
        data = newData;
        // 维护首/尾索引
        front = 0;
        tail = size;
    }

    @Override
    public E getFront() {
        if (isEmpty()) {
            throw new IllegalArgumentException("Queue is empty!");
        }
        return data[front];
    }

    @Override
    public boolean isEmpty() {
        return front == tail;  // 使用size == 0亦可
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append(String.format("Queue: size = %d , capacity = %d\n", size, getCapacity()));
        res.append("front [");
        for(int i = front ; i != tail ; i = (i + 1) % data.length){
            res.append(data[i]);
            if((i + 1) % data.length != tail)
                res.append(", ");
        }
        res.append("] tail");
        return res.toString();
    }

}
3. 利用LinkedList实现循环队列
public class LinkedListQueue<E> implements Queue<E> {
    /**
     * 内部类:节点
     */
    private class Node {
        /**
         * 实例域:节点元素、节点引用
         */
        public E e;
        public Node next;

        /**
         * 构造器:对实例域进行初始化
         * @param e 元素
         * @param next 引用指向
         */
        public Node(E e, Node next) {
            this.e = e;
            this.next = next;
        }

        public Node(E e) {
            this(e, null);
        }

        public Node() {
            this(null, null);
        }
    }

    /**
     * 实例域:首/尾指针、元素个数
     */
    private Node head, tail;
    private int size;

    /**
     * 无参构造器:用默认值对变量进行初始化
     */
    public LinkedListQueue() {
        head = null;
        tail = null;
        size = 0;
    }

    @Override
    public void enqueue(E e) {
        // 判断链表是否为空,若为空则在尾指针处新建节点
        if (tail == null) {
            tail = new Node(e);
            head = tail;
        } else {
            tail.next = new Node(e);
            tail = tail.next;
        }
        // 元素个数加一
        size++;
    }

    @Override
    public E dequeue() {
        // 判断队列是否为空
        if (isEmpty()) {
            throw new IllegalArgumentException("Queue is empty!");
        }
        // 将待删除的节点存入临时变量
        Node delNode = head;
        // 头指针后移
        head = head.next;
        // 将待删除节点指向空
        delNode.next = null;
        // 判断数组是否为空,若为空则将尾指针也赋为空
        if (head == null) {
            tail = null;
        }
        // 元素个数减一
        size--;
        // 返回临时变量所存储的元素
        return delNode.e;
    }

    @Override
    public E getFront() {
        if (isEmpty()) {
            throw new IllegalArgumentException("Queue is empty!");
        }
        return head.e;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append("Queue: front ");
        Node cur = head;
        while(cur != null) {
            res.append(cur.e + "->");
            cur = cur.next;
        }
        res.append("NULL tail");
        return res.toString();
    }

}
### 队列数据结构的特性 队列Queue)是一种线性的数据结构,其主要特点是 **先进先出(FIFO, First In First Out)** 原则[^2]。这意味着最早进入队列的元素会被最先移除。具体来说: - 当一个新元素加入到队列时,它总是被放置在队列的尾部(rear),这一过程通常称为 `push` 或者 `enqueue`。 - 而当需要从队列中删除或获取元素时,则始终是从队列的前端(front)进行操作,这被称为 `pop` 或者 `dequeue`。 这种机制使得队列非常适合用来管理按时间顺序排列的任务或者请求,在很多实际应用场景下都非常有用,比如操作系统中的进程调度、打印任务排队等场景。 以下是基于不同编程语言实现队列的一些基本方法示例: #### C语言实现队列 ```c #include <stdio.h> #define MAX_SIZE 100 typedef struct { int data[MAX_SIZE]; int front; int rear; } Queue; void initQueue(Queue *q) { q->front = q->rear = -1; } int isEmpty(Queue *q) { return (q->front == -1); } // 更多功能省略... ``` 此段代码展示了如何利用数组构建固定大小的循环缓冲区作为队列的基础框架[^1]。 #### Java中的队列接口及其应用 Java 提供了内置的支持来简化开发者对于复杂逻辑的操作需求。例如标准库提供了多种具体的队列实现实例像 LinkedList 和 PriorityQueue 等[^2]。 ```java import java.util.LinkedList; import java.util.Queue; public class Main { public static void main(String[] args){ Queue<String> queue = new LinkedList<>(); // 添加元素至队列末端 queue.add("A"); queue.offer("B"); System.out.println(queue.poll()); // 移除并返回头节点 A System.out.println(queue.peek()); // 查看而不移除头节点 B } } ``` 以上例子说明了通过调用相应的方法可以轻松完成入队(`add/offer`)与出队(`poll/remove`, 及查看不取出即`peek/element`)等功能。 #### 使用链表模拟动态调整容量的C++版本 另一种常见的方式就是采用双向指针构成单向链接列表形式表示整个序列状态变化情况如下图所示: ```cpp class Node{ public: int val; Node* next; }; class MyQueue { private: Node *head,*tail; public: /** Initialize your data structure here. */ MyQueue(): head(NULL), tail(NULL){} /** Push element x to the back of queue. O(1).*/ void push(int x){ auto newNode=new Node{x,nullptr}; if(!tail) head=tail=newNode; else{ tail->next=newNode; tail=newNode;} } /** Removes the element from in front of queue and returns that element.O(1)*/ int pop(){ int res=head->val; auto oldHead=head; head=head->next; delete oldHead; if(!head) tail=nullptr; return res; } /** Get the front element.Constant time complexity.*/ int peek() const{return head->val;} /** Returns whether the queue is empty or not.Constant time operation.*/ bool empty()const{return !head;} }; ``` 这里采用了手动内存分配方式创建节点对象,并维护两个成员变量分别指向当前有效范围内的首端位置以及终端边界处最后一个未使用的空间地址值以便后续扩展用途所需[^3]。 ### 结论 综上所述,无论是在哪种开发环境下选用何种底层存储媒介去诠释抽象概念上的“队列”,它们都严格遵守着同一个核心理念——那就是按照既定次序依次处理各项事务从而达到预期目标效果为止!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值