大话数据结构-队列

250 阅读7分钟

1 概述

  队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。

  队列是一种先进先出(First In First Out)的线性表,简称FIFO,允许插入的一端称为队尾,允许删除的一端称为队头。

image.png

2 抽象数据类型

ADT 队列(Queue)
    Data:同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。
    Operation:
        init(int maxSize):初始化操作,建立一个空栈;
        destroy():若栈存在,则销毁它;
        clear():清空栈;
        isEmpty():若栈为空,返回true,否则返回false;
	getHead():若队列存在且非空,返回队头元素;
	insert(DataType e):若队列存在,插入新元素到队尾;
	delete():若队列存在,删除队头元素;
        length():返回队列的元素个数;
endADT

  顺序存储的队列与线性表的顺序存储结构完全相同,但有一些问题:出队列的时候因为要从队头出,即下标为0的元素出,因此出队列时要移动其后的所有元素。因此通常,会使用循环队列来实现队列。

  头尾相接的顺序存储结构的队列,被称为循环队列。

3 顺序存储结构

3.1 队列满、队列空判断和队列实际元素数量

  循环队列引入了两个指针:
  (1) rear:指向队尾元素的下一个位置;
  (2) front:指向队头元素;

  同时,队列还有一个maxSize属性,表示该队列的最大长度。

  假设有一个最大长度为5的队列,初始化时,队列如下所示:

image.png

  这时,入队a1:

image.png

  接着入队a2、a3、a4:

image.png

  按照我们对队列的定义:rear永远指向一个空的元素空间,可知,此时队列已满,这时,rear + 1 = maxSize。

  这时,我们把a1和a2分别出队列:

image.png

  再将a5和a6入队列:

image.png

  根据队列定义,此时队列也已满,rear实际上比front大一圈,但从下标上看,只比front小1,也即rear + 1 = front,但如何表现大一圈呢?直接让(rear+1) % maxSize = front即可。

  而上文提到,在第一圈时,rear + 1 = maxSize也表示队列满了,而这种情况下,使用(rear + 1) % maxSize = front也适用。

  因此,队列满的条件是:

(rear + 1) % maxSize == front

  同理,队列长度的计算公式是:

(rear - front + maxSize) % maxSize

  队列为空则很简单,当初始化时,rear = front = 0,在上述操作的基础上,我们继续出队a3、a4,变成如下:

image.png

  继续出队a5:

image.png

  出队a6:

image.png

  此时队列为空,仍然是rear = front,因此,队列为空时:

rear == front

3.2 入队操作

  在3.1中已描述入队操作,即在队尾插入元素,并把rear往后挪一位即可:

/**
 * 入队列
 *
 * @param val 待进入队列的元素
 * @throws RuntimeException 当队列已满时抛出异常
 * @author Korbin
 * @date 2023-01-17 15:47:13
 **/
public void insert(T val) {
    if ((rear + 1) % maxSize == front) {
        // 队列已满
        throw new RuntimeException("fulled");
    }

    data[rear] = val;

    rear = (rear + 1) % maxSize;
}

3.3 出队操作

  在3.1中已描述出队操作,即把front指向的元素去除,并把front往后挪一位即可:

/**
 * 出队列
 *
 * @return 出队列的元素
 * @throws RuntimeException 队列为空时抛出异常
 * @author Korbin
 * @date 2023-01-17 15:49:25
 **/
public T delete() {
    if (isEmpty()) {
        // 队列为空
        throw new RuntimeException("empty queue");
    }
    T result = data[front];
    data[front] = null;
    front = (front + 1) % maxSize;
    return result;
}

3.4 完整代码

/**
 * 队列
 *
 * @author Korbin
 * @date 2023-01-17 11:54:53
 **/
public class Queue<T> {

    /**
     * 数据
     **/
    private T[] data;

    /**
     * 头指针
     **/
    private int front;

    /**
     * 队列最大元素数量
     **/
    private int maxSize;

    /**
     * 尾指针,若队列不空,指向队列尾元素下一个位置
     **/
    private int rear;

    /**
     * 清空队列
     *
     * @author Korbin
     * @date 2023-01-17 15:50:36
     **/
    public void clear() {
        init(maxSize);
    }

    /**
     * 出队列
     *
     * @return 出队列的元素
     * @throws RuntimeException 队列为空时抛出异常
     * @author Korbin
     * @date 2023-01-17 15:49:25
     **/
    public T delete() {
        if (isEmpty()) {
            // 队列为空
            throw new RuntimeException("empty queue");
        }
        T result = data[front];
        data[front] = null;
        front = (front + 1) % maxSize;
        return result;
    }

    /**
     * 返回队头元素
     *
     * @return 队头元素
     * @throws RuntimeException 队列为空时抛出异常
     * @author Korbin
     * @date 2023-01-17 15:52:33
     **/
    public T getHead() {
        if (isEmpty()) {
            // 队列为空
            throw new RuntimeException("empty queue");
        }
        return data[front];
    }

    /**
     * 初始化
     *
     * @author Korbin
     * @date 2023-01-17 15:43:34
     **/
    @SuppressWarnings("unchecked")
    public void init(int maxSize) {
        this.maxSize = maxSize;
        front = 0;
        rear = 0;
        data = (T[]) new Object[maxSize];
    }

    /**
     * 入队列
     *
     * @param val 待进入队列的元素
     * @throws RuntimeException 当队列已满时抛出异常
     * @author Korbin
     * @date 2023-01-17 15:47:13
     **/
    public void insert(T val) {
        if ((rear + 1) % maxSize == front) {
            // 队列已满
            throw new RuntimeException("fulled");
        }

        data[rear] = val;

        rear = (rear + 1) % maxSize;
    }

    /**
     * 判断队列是否为空,为空返回true,否则返回false
     *
     * @return 为空返回true,否则返回false
     * @author Korbin
     * @date 2023-01-17 15:51:32
     **/
    public boolean isEmpty() {
        return front == rear;
    }

    /**
     * 队列实际元素数量
     *
     * @return 实际元素数量
     * @author Korbin
     * @date 2023-01-17 15:44:51
     **/
    public int length() {
        return (rear - front + maxSize) % maxSize;
    }

}

4 链式存储结构

4.1 概述

  队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,我们把它简称为链队,为了操作上的方便,我们将队头指针指向单链表的头结点,队尾指针指向终端结点:

image.png

  空队列时,front和rear都指向头结点:

image.png

  因而,链队的结点结构与单链表一致,参考《大话数据结构-线性表》{:target="_blank"}。

4.2 入队

  已知,当链队为空时,rear指向头结点,当链队不为空时,rear指向终端结点,因此入队操作实现很简单:将原rear的后继结点改为待插入的结点,插入后这个“待插入的结点”变为终端结点,因rear是指向终端结点的,所以令rear指向这个“待插入的结点”,就完成了插入:

/**
 * 入队列
 *
 * @param val 待进入队列的元素
 * @throws RuntimeException 当队列已满时抛出异常
 * @author Korbin
 * @date 2023-01-17 15:47:13
 **/
public void insert(LinkListNode<T> val) {

    rear.setNext(val);

    rear = val;

    length++;
}

4.3 出队

  front指向头结点,头结点是不存储数据的,因此第一个结点应为front.next,出队,即取出front.next,并让front.next指向front.next.next即可。

  在出队时,如果只有一个结点时,rear指向了front.next,如果把front.next取出来后,队列为空队列,此时,应把rear也指向头结点,即rear = front:

/**
 * 出队列
 *
 * @return 出队列的元素
 * @throws RuntimeException 队列为空时抛出异常
 * @author Korbin
 * @date 2023-01-17 15:49:25
 **/
public LinkListNode<T> delete() {
    if (isEmpty()) {
        // 队列为空
        throw new RuntimeException("empty queue");
    }

    LinkListNode<T> node = front.getNext();

    front.setNext(node.getNext());

    if (rear.equals(node)) {
        // 如果删除的结点与队尾结点相同,则表示队列中只有一个结点,删除后队列为空,应让rear指向头结点
        rear = front;
    }

    length--;

    return node;
}

4.4 完整代码

/**
 * 链队列
 *
 * @author Korbin
 * @date 2023-01-17 16:43:35
 **/
public class LinkQueue<T> {

    /**
     * 头指针
     **/
    private LinkListNode<T> front;

    /**
     * 结点数量
     **/
    private int length;

    /**
     * 尾指针,若队列不空,指向队列尾元素下一个位置
     **/
    private LinkListNode<T> rear;

    /**
     * 清空队列
     *
     * @author Korbin
     * @date 2023-01-17 15:50:36
     **/
    public void clear() {
        init();
    }

    /**
     * 出队列
     *
     * @return 出队列的元素
     * @throws RuntimeException 队列为空时抛出异常
     * @author Korbin
     * @date 2023-01-17 15:49:25
     **/
    public LinkListNode<T> delete() {
        if (isEmpty()) {
            // 队列为空
            throw new RuntimeException("empty queue");
        }

        LinkListNode<T> node = front.getNext();

        front.setNext(node.getNext());

        if (rear.equals(node)) {
            // 如果删除的结点与队尾结点相同,则表示队列中只有一个结点,删除后队列为空,应让rear指向头结点
            rear = front;
        }

        length--;

        return node;
    }

    /**
     * 返回队头元素
     *
     * @return 队头元素
     * @throws RuntimeException 队列为空时抛出异常
     * @author Korbin
     * @date 2023-01-17 15:52:33
     **/
    public LinkListNode<T> getHead() {
        if (isEmpty()) {
            // 队列为空
            throw new RuntimeException("empty queue");
        }
        return front.getNext();
    }

    /**
     * 初始化
     *
     * @author Korbin
     * @date 2023-01-17 15:43:34
     **/
    public void init() {
        front = new LinkListNode<>();
        rear = front;
    }

    /**
     * 入队列
     *
     * @param val 待进入队列的元素
     * @throws RuntimeException 当队列已满时抛出异常
     * @author Korbin
     * @date 2023-01-17 15:47:13
     **/
    public void insert(LinkListNode<T> val) {

        rear.setNext(val);

        rear = val;

        length++;
    }

    /**
     * 判断队列是否为空,为空返回true,否则返回false
     *
     * @return 为空返回true,否则返回false
     * @author Korbin
     * @date 2023-01-17 15:51:32
     **/
    public boolean isEmpty() {
        return null != front && null != rear && front.getId().equals(rear.getId());
    }

    /**
     * 队列实际元素数量
     *
     * @return 实际元素数量
     * @author Korbin
     * @date 2023-01-17 15:44:51
     **/
    public int length() {
        return length;
    }

    @Override
    public String toString() {

        StringBuilder builder = new StringBuilder();
        if (null != front && null != rear) {
            builder.append("[front:{id=").append(front.getId()).append("},");
            LinkListNode<T> nextNode = front.getNext();
            while (null != nextNode) {
                builder.append(nextNode).append(",");
                nextNode = nextNode.getNext();
            }
            builder.append("rear:{id=").append(rear.getId()).append("}]");

        }
        return builder.toString();
    }

}