还不知道队列是什么?这么多年白排队了

144 阅读4分钟

一、队列定义

队列 (Queue) 是一种先进先出FIFO (First in First Out) 的线性存储结构。

队列的特殊性:

1. 元素只能从队列的队尾进入,从队头出去

2. 队列中各个元素的进出必须遵循“先进先出”的原则,即最先入队的元素必须最先出队

1.png

二、队列类型

1. 顺序队列

顺序队列指的是用顺序表模拟实现的队列存储结构。在编程语言中一般采用数组实现顺序表

初始化:定义长度为n的数组,定义两个变量 (front和rear) 分别记录队头和队尾的下标。初始化时front和rear都指向第0个位置。

初始.png

入队:存储新元素,并将rear + 1。当front等于0,rear等于n时,表示队列已满。

入队.png

出队:废弃当前第一个元素,并将front + 1。当front的值和rear相等时,表示队列已空。

出队.png

缺陷:

顺序队列前面的空闲空间无法再被使用,造成空间浪费

当顺序队列移动至顺序表尾部时,即便顺序表中有空闲空间,新元素也无法成功入队,一般称这种现象称为“假溢出”

解决办法:可进行数据搬移,重新利用空闲空间

数据搬移.png

但这样会增加时间复杂度,效率较低。循环队列能更有效的利用队列空间,且不需要移动数据。

代码:

public class ArrayQueue<T> {  
    // 存储数据的数组  
    private final T[] items;  
    // 数组容量  
    private final int n;  
    private int size = 0;  
    // front 记录队头索引 rear 记录队尾索  
    private int front = 0;  
    private int rear = 0;  
  
    // 申请一个指定容量的队列  
    public ArrayQueue(int capacity) {  
        items = (T[]) new Object[capacity];  
        n = capacity;  
    }  
  
    // 入队  
    public boolean enqueue(T item) {  
        // 队满  
        if (front == 0 && rear == n) {  
            return false;  
        }  
        // 数据搬移  
        if (front != 0 && rear == n) {  
            for (int i = front; i < rear; i++) {  
                items[i - front] = items[i];  
            }  
            rear = rear - front;  
            front = 0;  
        }  
        // 将数据加入队列  
        items[rear] = item;  
        rear++;  
        size++;  
        return true;  
    }
    // 出队  
    public T dequeue() {  
        if (front == rear)  
            return null;  
        T result = items[front];  
        front++;  
        size--;  
        return result;  
    }
}

2. 循环队列

所谓循环队列,本质仍是用顺序表模拟实现队列,只不过在具体实现的过程中,会将顺序表想象成首尾相连的环状表来用。

在循环队列中,末尾元素的下一个元素不是数组外,而是数组的头元素。这样就能够再次使用front之前的存储空间了。

初始化:定义长度为n的数组,定义两个变量 (front和rear) 分别记录队头和队尾的下标。初始化时front和rear都指向第0个位置。

循环队列.png

入队:存储新元素,rear = rear + 1,考虑到rear在n - 1处时再加1会超过范围 (数组长度为n,rear的范围为0 ~ n - 1),所以需要对rear + 1 模运算,以防其超过范围。即rear = (rear + 1) % n

循环队列入队.png

循环队列队满时front == rear,这和队空的条件一样,无法区分队空还是队满。为了解决该冲突,有三种方法:

① 当front和rear之间剩余一个空闲空间时,就视为队满

即rear + 1 = front则队满。考虑到rear在n - 1处时再加1会超过范围,所以需要对rear + 1 模运算,以防其超过范围。队满条件最终为 (rear + 1) % n = front

② 记录元素个数,以此来判断队满或队空。size = 0,入队size + 1,出队size - 1。或者直接取:

rear >= front:元素个数为size = rear - front

rear < front:元素个数为size = front - rear = n - (rear - front) = rear - front + n;

综合一下,元素个数为size = (rear - front + n) % n

循环队列队满.png

③ 定义一个标识flag,入队flag = 1,出队flag = 0。front == rear且flag == 1时队满;front == rear且flag == 0时队空

出队:废弃当前第一个元素,并将front + 1。当front的值和rear相等时,表示队列已空。

代码:

public class LoopArrayQueue<T> {  
    // 存储数据的数组  
    private final T[] items;  
    // 数组容量  
    private final int n;  
    private int size = 0;  
    private int flag = 0;  
    // front记录队头索引 rear记录队尾索引  
    private int front = 0;  
    private int rear = 0;  
  
    // 申请一个指定容量的队列  
    public LoopArrayQueue(int capacity){  
        items = (T[]) new Object[capacity];  
        n = capacity;  
    }  
  
    // 入队  
    public boolean enqueue(T item){  
        // 判断队满  
        // if ((rear + 1) % n == front) return false;  
        // if (size == n) return false;  
        if (front == rear && flag == 1) return false;  
        items[rear] = item;  
        rear = (rear + 1) % n;  
        size++;  
        flag = 1;  
        return true;  
    }  
  
    // 出队  
    public T dequeue(){  
        // 判断队空  
        // if(front == rear) return null;  
        // if (size == 0) return null;  
        if (front == rear && flag == 0) return null;  
        T result = items[front];  
        front = (front + 1) % n;  
        size--;  
        flag = 0;  
        return result;  
    }
}

3. 链式队列

链式队列,简称链队列,即使用链表实现的队列存储结构。链队列采用链式存储结构保存数据元素,允许添加无限多个数据元素,不会出现列满的问题。

链队列.png

入队:创建一个新节点,将原rear节点的next指向新节点,再将rear指向新节点

链队列入队.png

出队:将原front节点指向原front节点的next节点

链队列出队.png

代码:

public class LinkedQueue<T> {  
    // 定义一个节点类  
    private class Node{  
        T value;  
        Node next;  
    }  
    // 记录队列元素个数  
    private int size = 0;  
    // front指向队头结点 rear指向队尾节点  
    private Node front;  
    private Node rear;  
    // 申请一个队列  
    public LinkedQueue(){}  
    // 入队  
    public boolean enqueue(T item){  
        Node newNode = new Node();  
        newNode.value = item;  
        if (size == 0) front = newNode;  
        else rear.next = newNode;  
        rear = newNode;  
        size++;  
        return true;  
    }  
    // 出队  
    public T dequeue(){  
        if(size == 0) return null;  
        if(size == 1) rear = null;  
        T result = front.value;  
        front = front.next;  
        size--;  
        return result;  
    }
}

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情