# java

Java 数据结构篇-用链表、数组实现队列

2023-12-28 11:18:01
14

1.0 队列的说明

队列是一种线性数据结构,具有先进先出(First In First Out,FIFO)的特性。它类似于排队的概念,新元素被添加到队列的尾部,而从队列中移除元素时则从队列的头部开始。队列通常用于需要按照特定顺序处理元素的场景,比如任务调度、消息传递等。

1.1 队列的几种常用操作

- 队首元素(front):队列的头部元素。

- 队尾元素(rear):队列的尾部元素。

- 入队(offer):将新元素添加到队列的尾部。

- 出队(poll):从队列的头部移除元素。

- 获取队头元素(peek):从队列的头部获取元素,不移除。

- 判空(isEmpty):判断队列是否为空。

- 判满(isFull):判断队列是否已满(对于有限大小的队列)。

- 元素个数(size):获取队列有效的元素个数。

接口代码如下:

public interface QueueInterface{
    /**
     * 入列
     */
    boolean offer(int e);

    /**
     * 出列
     */
    int poll();

    /**
     * 获取队列头元素
     */
    int peek();

    /**
     * 获取队列中有效元素个数
     */
    int size();

    /**
     * 检验队列是否为空
     */
    boolean isEmpty();
}


2.0 使用链表实现队列说明

使用链表实现队列,无疑就是用链表的数据结构的方式来实现队列的操作(实现队列的接口)。跟栈不同的是:栈只能对一端进行操作,而队列是对头尾进行操作。一般对于单链表来说,头节点当作队列中的队头(front),尾节点当作队列中的队尾(rear)。而入队列相当于尾插,在链表尾部插入节点,出队列相当于头删,在链表头部删除头节点。对于双向链表来说,就比较自由了,头节点既可以作为队头,也可以作为队尾。尾节点既可以作为队头,也可以作为队尾。

2.1 链表实现队列

用双向链表来实现队列,既然头尾都可以作为队列,选其中一种即可,对于队尾来说也是如此。这里就用头节点作为队列中的队头,而尾节点作为队列中的队尾。

简单分析:

- 实现入栈操作:在链表尾部插入节点即可。

- 实现出栈操作:在链表头部删除节点即可。

- 实现获取队列头部元素:获取链表头部节点的元素即可。

- 实现总计有效元素个数:需要定义一个成员变量 size ,入栈时进行 size++ ,出栈时 size--

- 实现判空处理:当 size == 0 ,则为空队列。

- 实现判断满队列:当 size >= 创建队列时默认大小,则为满队列。

2.2 链表实现队列 - 入栈操作

实现入栈操作:在链表尾部插入节点即可。分为两种情况:当队列为空时,则入栈的节点即是队头也是队尾;当队列不为空时,则入栈的节点一般来说就是新的队尾。每一次入栈完成后,都需要进行 size++ 。

代码如下:

    //入列
    @Override
    public boolean offer(int e) {
        if (isEmpty()) {
            front = rear = new Node(null,e,null);
            size++;
            return true;
        }
        Node node = new Node(rear,e,null);
        rear.next = node;
        rear = node;
        size++;
        return true;
    }

2.3 链表实现队列 - 出栈操作

实现出栈操作:在链表头部删除节点即可。出栈一般分为三种情况:

第一种情况,当队列为空时,不应该出栈了,直接返回 -1 或者抛异常。

第二种情况,当只有一个节点时,出栈之后,就不会再有元素了,所以 front 与 rear 需要置为空。返回出栈节点的值即可。

第三种情况,除了第一、二种情况之后,第三种情况就可以正常的进行头删节点操作处理了。需要注意的是,出栈完成后,进行 size-- 。最后,返回出栈节点的数值即可。

代码如下:

    //出列
    @Override
    public int poll() {
        if (isEmpty()){
            return -1;
        }
        //注意特例:只有一个节点的情况
        if (front.next == null) {
            int temp = front.val;
            front = null;
            rear = null;
            return temp;
        }
        Node temp = front;
        front = front.next;
        front.prev = null;
        size--;
        return temp.val;
    }

2.4 链表实现队列 - 获取队头元素操作(不删除)

获取链表头部节点的元素即可。一般分为两种情况:当队列为空时,可以返回 -1 或者抛异常处理;当队列不为空时,直接返回头部节点的值即可。

代码如下:

    @Override
    public int peek() {
        if (isEmpty()) {
            return -1;
        }
        return front.val;
    }

2.5 链表实现队列 - 获取队列有效元素个数操作

直接返回 size 即可。

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

2.6 链表实现队列 - 判空处理操作

当 size == 0 时,返回 true 。否则返回 false 。也可以用 front == null 来判断是否为空队列。

代码如下:

    @Override
    public boolean isEmpty() {
        return front == null;
    }

2.7 用链表实现队列的完整代码

public class MyLinkedQueue implements QueueInterface {

    static class Node {
        public Node prev;
        public int  val;
        public Node next;

        public Node(Node prev, int val, Node next) {
            this.prev = prev;
            this.val = val;
            this.next = next;
        }
    }

    private Node front;
    private Node rear;
    private int size;

    //入列
    @Override
    public boolean offer(int e) {
        if (isEmpty()) {
            front = rear = new Node(null,e,null);
            size++;
            return true;
        }
        Node node = new Node(rear,e,null);
        node.prev = rear;
        rear.next = node;
        rear = node;
        size++;
        return true;
    }

    //出列
    @Override
    public int poll() {
        if (isEmpty()){
            return -1;
        }
        //注意特例:只有一个节点的情况
        if (front.next == null) {
            int temp = front.val;
            front = null;
            rear = null;
            return temp;
        }
        Node temp = front;
        front = front.next;
        front.prev = null;
        size--;
        return temp.val;
    }

    @Override
    public int peek() {
        if (isEmpty()) {
            return -1;
        }
        return front.val;
    }

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

    @Override
    public boolean isEmpty() {
        return front == null;
    }

}

3.0 使用数组实现循环队列说明

用数组实现队列,一般来说是循环队列,循环队列可以解决普通队列在出队操作后产生的空间浪费问题,同时可以利用数组的固定大小来实现队列的循环利用。

循环队列的接口代码如下:

public interface QueueInterface{
    /**
     * 入列
     */
    boolean offer(int e);

    /**
     * 出列
     */
    int poll();

    /**
     * 获取队列头元素,不删除
     */
    int peek();
    
    /**
     * 检验队列是否为空
     */
    boolean isEmpty();

    /**
     * 判断是否为满队列
     */
    boolean isFull();

    /**
     * 得到队尾元素,不删除
     */
    int Rear();
}

使用数组实现队列,无疑就是用数组的数据结构的方式来实现队列的操作(实现队列的接口)。循环队列的关键是使用两个指针来标记队列的头部和尾部,分别称为 front 和 rear 。当入队时,rear 指针向后移动;当出队时,front 指针向后移动。当 rear 指针到达数组的末尾时,可以通过取模运算将 rear 指针置为 0 ,实现循环的效果。


3.1 数组实现循环队列的操作

- 入队操作:将元素添加到 rear 指针所指向的位置,并更新 rear 指针。

- 出队操作:从 front 指针所指向的位置移除元素,并更新 front 指针。

- 判空操作:当 front 指针等于 rear 指针时,队列为空。

- 判满操作:当 (rear+1) % 数组长度等于 front 指针时,队列为满。

3.2 数组实现循环队列 - 入队列操作

将元素添加到 rear 指针所指向的位置,并更新 rear 指针。更新 rear 就是往后走一步,但是为了实现循环,可不能简单的进行 +1 处理,需要 rear = (rear + 1) % arr.length,这样就可以数组中循环走了。在入队前,需要先判断是否为满队列了。

代码如下:

    // 入队列
    @Override
    public boolean offer(int e) {
        if (isFull()) {
            return false;
        }
        arr[rear] = e;
        rear = (rear+1) % arr.length;
        return true;
    }

3.3 数组实现循环队列 - 出队列操作

从 front 指针所指向的位置移除元素,并更新 front 指针。同样的,更新可不能简单的 +1 处理,需要将 front = (front + 1) % arr.length ,每次出栈前需要记录一下出栈的元素,接着才往后走,最后返回记录的元素即可。出栈前,需要判断是否为空队列,如果为空队列,就需要抛异常或者返回 -1 处理。

代码如下:

    //出队列
    @Override
    public int poll() {
        if (isEmpty()) {
            return -1;
        }
        int temp = arr[front];
        front = (front + 1) % arr.length;
        return temp;
    }

3.4 数组实现队列 - 得到队头元素操作(不删除)

先判断是否为空队列,若不是,则返回队头元素即可。

代码如下:

    //得到队头元素,不删除
    @Override
    public int peek() {
        if (isEmpty()) {
            return -1;
        }
        return arr[front];
    }

3.5 数组实现队列 - 得到队尾元素操作(不删除)

先判断队列是否为空,如果为空队列,则返回 -1 或者抛异常。如果不为空,则需要返回 arr[rear - 1],但是需要注意的是:有一种情况需要额外考虑进去,当 rear == 0 时,那么 rear - 1 == -1 ,所以不合理,数组中的索引不存在 -1 。因此这个情况要分开讨论,当 rear == 0 时,那么需要返回的值为 arr[arr.length - 1];当 rear != 0 时,那么返回的值为 arr[rear - 1] 。

代码如下:

    //得到队尾元素,不删除
    @Override
    public int Rear() {
        if (isEmpty()) {
            return -1;
        }
        int temp = (rear == 0) ? arr.length - 1 : rear - 1;
        return arr[temp];
    }

3.6 数组实现队列 - 判空队列操作

当且仅当 rear == front 时,则队列为空。

代码如下:

    @Override
    public boolean isEmpty() {
        return front == rear;
    }

3.7 数组实现队列 - 判满队列操作

有很多种方式可以来判断队列是否满,比如,定义 size 变量来总计元素个数,然后跟 arr.lengrh 进行对比,相等即满队列了,不相等即为还没有满队列;使用标记也可以实现判断是否为满队列。

这里介绍牺牲空间来判断满队列,即当且仅当 (rear + 1) % arr.lengrh == front 时,该队列满了。由于这里牺牲了一个空间,所以需要在自定义初始化数组大小的时候额外 +1 。

代码如下:

    // 自定义数组大小
    public ArrayQueue(int capacity) {
        arr = new int[capacity + 1];
    }

    // 默认数组大小为: 10
    public ArrayQueue() {
        arr = new int[10];
    }
    @Override
    //判断是否为满队列(有很多种判断方法,这里使用牺牲空间方法来判断)
    public boolean isFull() {
        return (rear + 1) % arr.length == front;
    }


3.8 用数组实现队列完整代码

public class ArrayQueue implements QueueInterface{

    private int front;
    private int rear;
    private int[] arr;

    // 自定义数组大小
    public ArrayQueue(int capacity) {
        arr = new int[capacity + 1];
    }

    // 默认数组大小为: 10
    public ArrayQueue() {
        arr = new int[10];
    }

    // 入队列
    @Override
    public boolean offer(int e) {
        if (isFull()) {
            return false;
        }
        arr[rear] = e;
        rear = (rear+1) % arr.length;
        return true;
    }

    //出队列
    @Override
    public int poll() {
        if (isEmpty()) {
            return -1;
        }
        int temp = arr[front];
        front = (front + 1) % arr.length;
        return temp;
    }

    //得到队头元素,不删除
    @Override
    public int peek() {
        if (isEmpty()) {
            return -1;
        }
        return arr[front];
    }

    //得到队尾元素,不删除
    @Override
    public int Rear() {
        if (isEmpty()) {
            return -1;
        }
        int temp = (rear == 0) ? arr.length - 1 : rear - 1;
        return arr[temp];
    }

    @Override
    public boolean isEmpty() {
        return front == rear;
    }

    @Override
    //判断是否为满队列(有很多种判断方法,这里使用牺牲空间方法来判断)
    public boolean isFull() {
        return (rear + 1) % arr.length == front;
    }
    
}

最后编辑于 2024-11-24 19:52:55