Java 数据结构篇-用链表、数组实现队列(数组实现:循环队列)

🔥博客主页: 【小扳_-CSDN博客】
❤感谢大家点赞👍收藏⭐评论✍
 

文章目录

        1.0 队列的说明

        1.1 队列的几种常用操作

        2.0 使用链表实现队列说明

        2.1 链表实现队列

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


        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 或者抛异常。

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

        第三种情况,除了第一、二种情况之后,第三种情况就可以正常的进行头删节点操作处理了。需要注意的是,出栈完成后,进行 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;
    }
    
}