世上有两种最耀眼的光芒,一种是太阳,一种是我们努力的模样。

什么是队列?

在日常生活中队列很常见,像我们经常排队购物或购票,排队是体现了“先来先服务”的原则。 队列在计算机系统中的应用也非常广 泛。例如: 操作系统中的作业排队。在多道程序运行的计算机系统中,可以同时有多个作业运行,它们的运算结果都需要通过通道输出,若通道尚未完成输出,则后来的作业应排队等待,每当通道完成输出时,则从队列的队头退出作业进行输出操作,而凡是申请该通道输出的作业都从队尾进人该队列等待。
数据结构:线性结构之队列_数据结构
队列(Queue)是另一种限定性线性表,它只允许插人在表的一端进行, 而删除在表的另一端进行,我们将这种数据结构称为队或队列,把允许插人的一端叫队尾( rear),把允许删除的一端叫队头 ( front)。队列的插人操作通常称为人队列或进队列,而队列的删除操作则称为出队列或退队列。当队列中无数据元素时,称为空队列。根据队列的定义可知,队头元素总是最先进队列的,也总是最先出队列;队尾元素总是最后进队列,因而也是最后出队列。这种表是按照先进先出(FIFO,first in first out )的原则组织数据的,因此,队列也被称为“先进先出”表。这与
我们日常生活中的排队是一致的。

队列的存储结构

与线性表、栈类似,队列也有顺序存储和链式存储两种存储方法。

(一)顺序队列

队列的顺序存储结构可以简称为顺序队列,也就是利用-组地址连续的存储单元依次存 放队列中的数据元素。一般情况下,我们使用一维数组来作为队列的顺序存储空间,另外再设立两个指示器:一个为指向队头元素位置的指示器front,另个为指向队尾的元素位置的指示器rear。
数据结构:线性结构之队列_java_02

C语言实现顺序队列
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 5
#define dataType int

typedef struct{
    dataType data[MAXSIZE];
    int rear;
    int front;
}CSeQueue;

//置空
CSeQueue* Init_CSeQueue(){

    CSeQueue *queue = (CSeQueue *) malloc(sizeof(CSeQueue));
    if (queue == NULL) {
        return 0;
    }
    queue->rear = 0;
    queue->front = 0;
    return queue;
}

//非空判断
int IsEmpty_CSeQueue(CSeQueue *queue) {

    if (queue->front == queue->rear) {
        return 1;
    } else {
        return 0;
    }
}

//判满
int IsFull_CSeQueue(CSeQueue *queue){

    if (queue->rear == MAXSIZE) {
        return 1;
    }
    return 0;
}

//入队
int In_CSeQueue(CSeQueue *queue, dataType data) {

    if (IsFull_CSeQueue(queue)) {
        return 0;
    }

    queue->data[queue->rear] = data;
    queue->rear++;
    return 1;
}

//出队
int Out_CSeQueue(CSeQueue *queue, dataType *data) {

    if (IsEmpty_CSeQueue(queue)) {
        return 0;
    }

    *data = queue->data[queue->front];
    queue->front++;
    return 1;
}

存在的问题:

数据结构:线性结构之队列_数据结构_03
建立的空队及入队、出队的示意图如上,设MAXSIZE=5。从图中可以看到,随着
入队、出队的进行,会使整个队列整体向后移动,就会出现队尾指针一静一动到了最后,再有元素入队就会出现“假溢出”,而事实上此时队中并未真的"满员",这是由于“队尾入、队头出”这种受限制的操作所造成的。

循环队列

解决假溢出的方法之一是将 队列的数据区data[0…MAXSIZE-1]看成头、尾相接的循环结构,即规定最后一个单元的后继为第一个单元, 这样整个数据区就像 一个环,我们形象地称之为循环队列。头、尾指针的关系不变,即头指示器front总是指向队列中实际队头元素的前面一个位置,而尾指示器rear总是指向队尾元素。
数据结构:线性结构之队列_java_04

C语言实现循环队列
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 6
#define dataType int

typedef struct{
    dataType data[MAXSIZE];
    int rear;
    int front;
}CSeQueue;

//置空
CSeQueue* Init_CSeQueue(){

    CSeQueue *queue = (CSeQueue *) malloc(sizeof(CSeQueue));
    if (queue == NULL) {
        return 0;
    }
    queue->rear = 0;
    queue->front = 0;
    return queue;
}

//非空判断
int IsEmpty_CSeQueue(CSeQueue *queue) {

    if (queue->front == queue->rear) {
        return 1;
    } else {
        return 0;
    }
}

//判满
int IsFull_CSeQueue(CSeQueue *queue){

    if ((queue->rear + 1)%MAXSIZE == queue->front) { //浪费一个空间,用于区分队空和队满
        return 1;
    } else {
        return 0;
    }
}

//入队
int In_CSeQueue(CSeQueue *queue, dataType data) {

    if (IsFull_CSeQueue(queue)) {
        return 0;
    }

    queue->data[queue->rear] = data;
    queue->rear = (queue->rear + 1) % MAXSIZE;
    return 1;
}

//出队
int Out_CSeQueue(CSeQueue *queue, dataType *data) {

    if (IsEmpty_CSeQueue(queue)) {
        return 0;
    }

    *data = queue->data[queue->front];
    queue->front = (queue->front + 1 ) % MAXSIZE;
    return 1;
}
Java实现循环队列
package cn.boom.queue;

import java.util.Arrays;

public class SeqQueue<T> implements Queue<T> {

    private T data[];
    private int front;
    private int rear;
    private int size;

    public SeqQueue() {

        this.data = (T[]) new Object[11];
        this.front = 0;
        this.rear = 0;
        this.size = 0;
    }

    public SeqQueue(int capacity) {

        this.data = (T[]) new Object[capacity + 1];
        this.front = 0;
        this.rear = 0;
        this.size = 0;
    }

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

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

    @Override
    public void enQueue(T e) {

        if ((rear + 1) % getCapacity() == front) { //队满
            updateCapacity(data.length * 2);
        }

        data[rear] = e;
        this.rear = (this.rear + 1) % getCapacity();
        size++;
    }

    @Override
    public T deQueue() {

        if (isEmpty()) {
            throw new IllegalArgumentException("The Queue is null !");
        }

        T elem = data[front];
        this.front = (this.front + 1) % getCapacity();
        size--;

        if (size < getCapacity() / 2) {
            updateCapacity(getCapacity() / 2);
        }

        return elem;
    }

    @Override
    public T getFront() {

        if (isEmpty()) {
            throw new IllegalArgumentException("The Queue is null !");
        }
        return data[front];
    }

    /**
     * 动态扩容缩容
     * @param newCapacity
     */
    private void updateCapacity(int newCapacity) {

        T[] newData = (T[]) new Object[newCapacity];

        for (int i = 0; i < this.size; i++) {

            newData[i] = this.data[(i + front) % getCapacity()];
        }

        this.data = newData;
        this.front = 0; //
        this.rear = size;
    }

    /**
     * 获取容量
     * @return
     */
    private int getCapacity(){
        return data.length;
    }

    @Override
    public String toString() {

        StringBuilder res = new StringBuilder();
        res.append("SeqQueue{ data = [");

        for (int i = front; i != rear; i = (i + 1) % getCapacity()) {

            if ((i + 1) % getCapacity() != rear) {
                res.append(data[i] + ", ");
            } else {
                res.append(data[i]);
            }

        }
        res.append("]");
        res.append(", front" + front);
        res.append(", rear=" + rear);
        res.append(", size=" + size);
        res.append(", capacity=" + (getCapacity() - 1));
        res.append("}");
        return res.toString();
    }
}

(二)链队列

循环队列虽然解决了"假溢出"问题,但若用户无法预计所需队列的最大空间,我们就只能采用链式结构来存储队列。链式存储的队列称为链队列。
和链栈类似,可以用单链表来实现链队列,根据队列的FIFO原则,链队列中为了操作方便,可以采用带头结点的单链表表示队列,并设置一个队头指针(front)和一个为指针(rear)。
数据结构:线性结构之队列_链表_05

C语言实现链队列
#include <stdio.h>
#include <stdlib.h>

#define dataType int

typedef struct QueueNode {
    dataType data;
    struct QueueNode *next;
} Node;

typedef struct {
    Node *front;
    Node *rear;
} slQueue;

//置空
slQueue *Init_slQueue() {
    slQueue *queue = (slQueue *) malloc(sizeof(slQueue));
    queue->front = (Node *) malloc(sizeof(Node));//初始化头节点
    queue->rear = queue->front;
    return queue;
}

//判空
int IsEmpty_slQueue(slQueue *queue) {

    if (queue->front == queue->rear) {
        return 1;
    }
    return 0;
}


//入队 : rear队尾入队
int In_slQueue(slQueue *queue, dataType data) {

    Node *node = (Node *) malloc(sizeof(Node));
    if (node == NULL) {
        return 0;
    }
    node->data = data;
    node->next = NULL;
    queue->rear->next = node;
    queue->rear = node;
    return 1;
}

//出队 头结点后做队首
int Out_slQueue(slQueue *queue, dataType *data) {

    if (IsEmpty_slQueue(queue)) {
        return 0;
    }

    *data = queue->front->next->data;

    if (queue->front->next == queue->rear) {
        queue->rear = queue->front;
        queue->front->next = queue->front->next->next;
    } else {
        queue->front->next = queue->front->next->next;
    }
    return 1;
}

Java实现链队列

复用线性表中的单链表LinkedList,很方便的实现了链队列。
数据结构:线性数据结构

package cn.boom.queue;

public class LinkedListQueue<T> implements Queue<T> {

    private LinkedList<T> linkedList;

    public LinkedListQueue(){
        linkedList = new LinkedList<T>();
    }

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

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

    @Override
    public void enQueue(T e) {
        linkedList.addLast(e);
    }

    @Override
    public T deQueue() {
        return linkedList.remove(0);
    }

    @Override
    public T getFront() {
        return linkedList.get(0);
    }

    @Override
    public String toString() {
        return "LinkedListQueue{" +
                "linkedList=" + linkedList +
                '}';
    }
}

参考文献

[1]王曙燕.数据结构与算法:人民邮电出版社