数据结构---循环队列与循环双端队列的实现(Java实现)

简介: 队列的底层用双向链表实现,因为使用双向链表保证了入队列和出队列的时间复杂度都达到O(1),那能否使用一段连续的空间实现呢?当然可以,先分析用普通的数组对其实现进行分析,看看会出现哪些问题?

🚁分析如何设计循环队列

队列的底层用双向链表实现,因为使用双向链表保证了入队列和出队列的时间复杂度都达到O(1),那能否使用一段连续的空间实现呢?当然可以,先分析用普通的数组对其实现进行分析,看看会出现哪些问题?


用front标记对头元素,进行出队列,用rear标记队尾后的空位置,进行入队列


🚧入队列操作:直接在rear标记的位置进行插入,再进行rear++,时间复杂度为O(1)


🏁出队列操作:有两种实现方式

方式1:将front位置的元素出队列后,front位置不变,将front后面的元素都向前搬移一个长度,时间复杂度O(n)

方式2:将front位置元素出队列后,再进行front++,时间复杂度O(1),但是会有一个问题,造成空间的假溢出

image.png

为了解决上述空间的假溢出问题,使用循环数组来实现循环队列,所谓循环数组,就是数组尾部标记往后再走的话就走到数组头部了

image.png



为了更好的理解循环队列,我们这样画图表示:

image.png



但是这样会出现一个问题:发现当rear与front在同一位置时,有两种状态,队列满,队列空,那么如何判断队列满与队列空❓

微信图片_20221029162728.png



🤔思考怎么区分队列满与队列空


🚀如何区分循环队列的满与空?

有三种办法来区分循环队列的满与空


🔥方法一:使用count来标记队列中元素的个数,当count为0说明队列为空,当count等于数组的长度时说明队列已经满了

💧方法二:预留一个空位置,也就是当队列还有一个空位置的时候,认为该循环队列已经满了


image.png


⚡方法三:使用一个标记flag,初始时设置为false,每入队一个元素将flag设置为true

image.png


对比上述三种实现方式,使用count记录队列元素的个数辨识度更高 ,所以后续在实现的时候就使用count标记来判断队列的空与满


🛸实现循环队列

循环队列的成员变量:

private E[] array;
    private int front; //标记队列头,进行出队列
    private int rear; //标记队列尾,进行入队列
    private int count; //队列中元素个数,用来区分队列满与空
    private int N; //数组的长度

 

循环队列的构造方法:

public MyCircularQueue(){
        array = (E[])new Object[10];
        N = array.length;
    }

 

入队列操作:


1. 如果队列已经满了,返回false

2. 否则在队尾位置直接插入元素,count++,队尾标记rear往后走一个

3.  如果rear走到N,则将rear置为0,从头开始继续

image.png


public boolean enQueue(E val){
        if(isFull()){
            return false;
        }
        array[rear] = val;
        rear++;
        if(rear == N){
            rear = 0;
        }
        count++;
        return true;
    }
出队列操作:
1. 如果队列已经为空,则返回false
2. 否则直接将count--,front++
3. 如果front走到N的位置,将front置为0,从头开始,与上述类似
    public boolean deQueue(){
        if(isEmpty()){
            return false;
        }
        count--;
        front++;
        if(front == N){
            front = 0;
        }
        return true;
    }


获取队尾元素:


如果队列为空抛出异常,否则直接返回rear前一个位置的元素,因为rear标记的是队尾的空位置,对rear的处理有两种情况,所以要对两种情况统一处理,获取(rear-1+N)%N位置元素

public E rear(){
        if(isEmpty()){
            throw new RuntimeException("队列为空");
        }
        return array[(rear-1+N)%N];
    }

image.png

获取对头元素:


如果队列为空,则抛出异常,否则直接返回front位置元素,因为front标记的是队头元素

public E front(){
        if(isEmpty()){
            throw new RuntimeException("队列为空");
        }
        return array[front];
    }

 

判断队列是否为空:


比较count==0

public boolean isEmpty(){
        return count==0;
    }

 

判断是否队列已经满了:


比较count==N

public boolean isFull(){
        return count==N;
    }

 

🪂了解双端队列Deque

Deque是一个接口,底层也是用LinkedList实现的,双端队列指队列的两端都可以进行入队列和出队列操作

image.png



🛰️循环双端队列的实现

循环双端队列,与循环队列的实现大同小异,只不过是在队列头多了入队列操作,在队列尾多了出队列操作


🚨注意:


在循环队列中,front标记对头元素,rear标记队尾空位置

在循环双端队列中,front标记队头空位置,rear标记队尾元素


循环双端队列的成员变量:

int[] array;
    int rear; //队尾
    int front; //队头
    int count; //队列中元素个数
    int N; //数组长度

 

循环双端队列的构造方法:

public MyCircularDeque() {
        array = new int[10];
        N = array.length;
    }

 

队列头部插入:


1. 如果队列已经满了,直接返回false

2. 否则在front位置直接插入元素,然后front--,count++

3. 如果front当前为0,front--直接就为-1,所以要进行(front+N)%N计算更新front

public boolean insertFront(int value) {
        if(isFull()){
            return false;
        }
        array[front] = value;
        count++;
        front--;
        front = (front+N)%N;
        return true;
    }

 

队列尾部插入:


1. 如果队列为空,则直接返回false

2. 否则在rear+1位置插入元素,如果rear+1等于数组长度时,需要置0,所以进行rear%N操作

3. 插入后count++

public boolean insertLast(int value) {
        if(isFull()){
            return false;
        }
        rear++;
  rear %= N;
  array[rear] = value;
  count++;
  return true;
    }

 

队列头部删除:


1. 如果队列为空,直接返回false

2. 否则将front往后走一个位置,如果走到N的位置,则需要将front重新置0,所以(front+1)%N

3. 删除完,count--

public boolean deleteFront() {
        if(isEmpty()){
            return false;
        }
        front = (front+1)%N;
        count--;
        return true;
    }

 

队列尾部删除:


1. 如果队列为空,则直接返回false

2. 否则rear往前走一个位置,如果走到-1,则要将rear置为末尾,所以(rear-1+N)%N

3. 删除后,count--

public boolean deleteLast() {
        if(isEmpty()){
            return false;
        }
        rear = (rear-1+N)%N;
        count--;
        return true;
    }

 

获取队头元素:


1. 如果队列为空,则返回-1

2. 否则返回front下一个位置的元素,如果front下一个位置为N,则将front置为0,所以(front+1)%N

3. 返回array[(front+1)%N]

public int getFront() {
        if(isEmpty()){
            return -1;
        }
        return array[(front+1)%N];
    }

 

获取队尾元素:


1. 如果队列为空,则直接返回-1

2. 直接返回rear位置的元素

public int getRear() {
        if(isEmpty()){
            return -1;
        }
        return array[rear];
    }

 

判断队列是否为空:


直接判断count==0

public boolean isEmpty() {
        return count==0;
    }

 

判断队列是否已经满了:


直接判断count==N

public boolean isFull() {
        return count==N;
    }

 


目录
打赏
0
0
0
0
4
分享
相关文章
|
4月前
|
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
477 9
|
20天前
|
《从头开始学java,一天一个知识点》之:循环结构:for与while循环的使用场景
**你是否也经历过这些崩溃瞬间?** - 看了三天教程,连`i++`和`++i`的区别都说不清 - 面试时被追问"`a==b`和`equals()`的区别",大脑突然空白
64 22
|
2月前
|
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
176 77
|
1月前
|
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
30 11
|
5月前
|
Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。
【10月更文挑战第19天】本文详细介绍了Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。HashMap以其高效的插入、查找和删除操作著称,而TreeMap则擅长于保持元素的自然排序或自定义排序,两者各具优势,适用于不同的开发场景。
70 1
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
Java快速入门之判断与循环
本文介绍了编程中的流程控制语句,主要包括顺序结构、判断结构(if语句和switch语句)以及循环结构(for、while和do...while)。通过这些语句可以精确控制程序的执行流程。if语句有三种格式,分别用于简单条件判断、二选一判断和多条件判断。switch语句适用于有限个离散值的选择判断,而循环结构则用于重复执行某段代码,其中for循环适合已知次数的情况,while循环适合未知次数但有明确结束条件的情况,do...while则是先执行后判断。文中还提供了多个示例和练习,帮助读者理解并掌握这些重要的编程概念。
|
2月前
|
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
83 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
JAVA线程池有哪些队列? 以及它们的适用场景案例
不同的线程池队列有着各自的特点和适用场景,在实际使用线程池时,需要根据具体的业务需求、系统资源状况以及对任务执行顺序、响应时间等方面的要求,合理选择相应的队列来构建线程池,以实现高效的任务处理。
156 12
|
2月前
|
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
62 9