<JavaDS> 队列(Queue)、双端队列(Deque)、循环队列

目录

一、队列(Queue)

1.1 队列(Queue)概述

1.2 队列(Queue)的实现

1.3 队列(Queue)的方法

1.4 队列的应用场景

二、双端队列(Deque)

2.1 双端队列(Deque)概述

2.2 双端队列(Deque)的方法

三、循环队列

3.1 循环队列概述

3.2 实现数组下标的循环

3.3 判断循环队列的状态

3.4 循环队列的增删查操作

3.5 获取循环队列存储的元素个数


一、队列(Queue)

1.1 队列(Queue)概述

•  队列(Queue)是一种经常使用的集合,是实现了一个先进先出(FIFO:First In First Out)的特殊线性表,因此又称为FIFO 队列。

•  队列(Queue)只允许在一端进行入队列操作,用于插入数据;在另一端进行出队列操作,用于删除数据。        
        入队列:进行插入操作的一端称为队尾(Tail/Rear);
        出队列:进行删除操作的一端称为队头(Head/Front);


1.2 队列(Queue)的实现

•  队列(Queue)的底层实现可以通过链表数组两种方式

        (1) 链表实现:链表是一种动态数据结构,可以动态地分配内存。队列的每个元素都是一个节点,节点包含数据和指向下一个节点的指针。当队列满时,只需要添加一个新节点到链表尾部,而不需要像数组一样重新分配内存。因此,链表实现的队列具有较高的空间利用率,但查找操作则开销较大。

        (2) 数组实现:数组是一种静态数据结构,在创建时必须确定长度。队列的每个元素都存储在数组的指定位置。数组可以进行随机查找操作,但在增删元素时开销较大,需要移动其他元素以保持队列的顺序。


1.3 队列(Queue)的方法

•  队列(Queue)中,有两组功能一样,但执行失败时给出的反馈不一样的方法,如下表展示:

方法功能抛出异常的方法返回false或null的方法
添加尾元素add(E e)boolean offer(E e)
删除首元素E remove()E poll()
查看首元素E element()E peek()

•  应注意尽量避免将null添加到队列中。因为poll()方法在删除元素失败时,将会返回null,如果队列中存在null,此时将无法判断是否成功删除元素;


1.4 队列的应用场景

•  列举队列的部分应用场景:

        1. 线程池中的任务队列:线程池通过队列来管理待执行的任务。

        2. 消息队列:消息队列用于在不同的进程或线程之间传递消息。

        3. 缓冲区:缓冲区通过队列来实现等待或缓冲数据。


二、双端队列(Deque)

2.1 双端队列(Deque)概述

•  双端队列(Deque)是一种可以在队列的两端进行插入和删除操作的数据结构,deque 是 “double ended queue” 的简称。与普通队列(Queue)不同,双端队列允许在队列的两端进行操作

•  双端队列(Deque)继承了接口 Queue 。

public interface Deque<E> extends Queue<E>

 •  ArrayDequeLinkedList 是双端队列(Deque)的实现类

Deque<E> arrayDeque = new ArrayDeque<>();
Deque<E> linkedList = new LinkedList<>();


2.2 双端队列(Deque)的方法

•  双端队列(Deque)中,同样有两组功能一样,但执行失败时给出的反馈不一样的方法,如下表展示:

方法功能抛出异常的方法返回false或null的方法
添加首元素addFirst(E e)boolean offerFirst(E e)
添加尾元素addLast(E e)boolean offerLast(E e)
删除首元素E removeFirst()E pollFirst()
删除尾元素E removeLast()E pollLast()
查看首元素E getFirst()E peekFirst()
查看尾元素E getLast()E peekLast()

•  双端队列(Deque)中,也包含了自己的 push(E e) 和 E pop() 方法,这两个方法的命名和使用与栈(Stack)类相同。当前Java官方推荐使用双端队列(Deque)来代替栈,栈(Stack)类被认为已过时。


三、循环队列

3.1 循环队列概述

•  循环队列并不是一个单独的类(class)或接口(interface),只是一种数据的组织形式,是一种特殊的队列。

•  循环队列通常使用数组实现,具有头指针(front)和尾指针(rear),分别指向队头元素和队尾元素。

•  循环队列通常只在队列的两端进行操作,是实现了一个先进先出(FIFO:First In First Out)的特殊队列。

•  最大下标的后继是最小下标,最小下标的前驱是最大下标,形成一个环状队列。

 

•  循环队列的优点

        (1)节省空间:循环队列可以重复利用队列空间。

        (2)提高效率:循环队列的删除和添加操作的时间复杂度均为O(1),操作效率高。

•  循环队列经常被应用于队列缓冲区,在数据传输、数据处理等场景中,经常需要使用队列来缓冲数据。循环队列可以节省空间,提高效率,因此常常被用来作为队列缓冲区。


3.2 实现数组下标的循环

•  循环队列通常使用数组实现,要实现队列的循环,就必须实现数组下标最大值和最小值之间的转换,如下图:

•  数组下标循环的技巧

        (1)下标最大值向最小值转换:偏移后下标 = (当前下标 + 偏移量) % 数组长度

        (2)下标最小值向最大值转换:  偏移后下标 = (当前下标 + 数组长度 - 偏移量) % 数组长度

                偏移量:是指每次增加或减少元素后,下标指针移动的幅度;

例:假设偏移量为1;

        由下标 7 向下标 0 转换;

                偏移后下标 = (当前下标 + 偏移量) % 数组长度

                偏移后下标 = (7 + 1) % 8

                偏移后下标 = 0

        由下标 0 向下标 7 转换;

                偏移后下标 = (当前下标 + 数组长度 - 偏移量) % 数组长度

                偏移后下标 = (0 + 8 - 1) % 8

                偏移后下标 = 7


3.3 判断循环队列的状态

•  循环队列可以通过以下三种方式判断队列为空或已满: 

        (1)在类中设置 size 属性记录,每增删一个元素,size 对应增加或减少。当 size 为 0 时,队列为空;当 size 与数组的长度一样时,队列为满。

        (2)设置一个标志,假设当出队列时,标志为 false ,当入队列时,标志为 ture 。每次出入队列后,判断头尾指针是否重合,如果重合且标志为 false ,则表示出队列后,头尾指针重合,此时队列为空;如果重合且标志为 ture ,则表示入队列后,头尾指针重合,此时队列为满。

        (3)保留一个空间不放元素,用于表示数组是否已满。在队列中保留一个空的空间,尾指针永远指向该空间。根据头尾指针是否重合,判断队列是否为空;根据尾指针的后继是否为头指针,判断队列是否为满。

•  保留一个空间不放元素,判断队列状态的代码实现

    //判断空队列方法;
    public boolean isEmpty() {
        return this.front == this.rear;
    }
    //判断满队列方法;
    public boolean isFull() {
        return this.front == (this.rear+1) % this.elem.length;
    }

 (下文中,循环队列其他操作的实现代码都沿用保留一个空间不放元素的方式)


3.4 循环队列的增删查操作

//入队列方法;
    public boolean enQueue(int value) {
        /*判断数组是否已满,
          已满则返回false;
          没满赋值,移动rear指针到下一下标;
          返回true;*/

        if(isFull()){
            return false;
        }
        this.elem[this.rear] = value;
        this.rear = (this.rear+1) % this.elem.length;
        return true;
    }
//出队列方法;
    public boolean deQueue() {
        /*判断数组是否为空,
          为空则返回false;
          否则将elem[front]置空,
          移动front到下一下标;
          返回true;*/

        if(isEmpty()){
            return false;
        }
        this.elem[this.front] = null;
        this.front = (this.front+1) % this.elem.length;
        return true;
    }
//查看头元素方法;
    public int getFront() {
        if(isEmpty()){
            //抛出异常,队列为空;
        }
        return this.elem[this.front];
    }
//查看尾元素方法;
    public int getRear() {
        if(isEmpty()){
            //抛出异常,队列为空;
        }
        //尾指针永远指向空的元素,如果尾指针位于0下标,则尾元素下标为最大下标;否则尾元素下标为当前空元素的下标-1;
        int index = (this.rear == 0 ? this.elem.length-1 : this.rear-1) ;
        return this.elem[index];
    }

3.5 获取循环队列存储的元素个数

•  由于 尾指针rear 指向数组尾元素的后继,因此数组求有效元素个数size时,可以使用 rear - front 的计算方式得到。

例:front为0,rear为3:

        size = rear - front

        size = 3 - 0

        size = 3

 

•  但是由于当前队列是一个循环队列,那么将可能出现头指针下标大于尾指针前驱的下标的情况。此时再使用 rear - front 的计算方式求有效元素个数,将得到一个负数。

 例:front为3,rear为1:

        size = rear - front

        size = 1 - 3

        size = -2

 

•  得到的有效元素个数为负数,显然是不正确的。

•  此时假设数组最大容量为MAXSIZE,将 这个值为负数的差 + MAXSIZE 就可以得到正确的有效元素个数。

例:front为3,rear为1,MAXSIZE为8:

        size = rear - front + MAXSIZE

        size = 1 - 3 + 8

        size = 6

•  由此可以得出结论,当 front <= rear 时,有效元素个数等于 rear - front ;当 front > rear 时,有效元素个数等于 rear - front + MAXSIZE ;

•  通过if else语句实现代码:

//获取队列中存储的有效元素个数;
    public int getSize() {
        if (this.front <= this.rear) {
            return this.rear - this.front;
        } else {
            return this.rear - this.front + this.MAXSIZE;
        }
    }

•  上述代码中, rear - front 为负数的情况下,使用公式 rear - front + MAXSIZE 进行处理,返回一个正确反映有效元素个数的正数;而使用 MAXSIZE 对 rear - front + MAXSIZE 进行取模,得到公式 (rear - front + MAXSIZE) % MAXSIZ ,则可以有一条公式正确处理正负两种情况的效果,代码更为简洁。

•  通过公式 (rear - front + MAXSIZE) % MAXSIZ 实现代码:

//获取队列中存储的有效元素个数
    public int size() {
        return (this.rear - this.front + this.MAXSIZE) % this.MAXSIZE;
    }

( 哈哈哈~~ 文章结束!) 

( 看到这里,如果有为各位帅哥美女提供一点点灵感,请点一个小小的赞哦,比心💖💖💖 )

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值