Java循环队列原理与用法详解
作者:WFaceBoss 发布时间:2023-11-13 20:05:36
本文实例讲述了Java循环队列原理与用法。分享给大家供大家参考,具体如下:
在正式进行循环队列学习之前,我们先来看看在顺序队列中删除队首元素出现的问题
(1)设一个容量为capacity=8,size=5(a,b,c,d,e)的数组,左侧为队首、右侧为队尾。
(2)出队一个元素后,需整体往前移动一位
#出队
#2整体前移一位
关于该种操作方式我们很容易得出时间复杂度为O(n)。
这时我们就想可不可以在出队元素后,整体元素不往前移,而是在数组中记下队首front是谁,同时队尾tail指向在下一次元素入队时的位置,这样当再有出队时只需要维护一下front的指向即可,而不需移动元素。就这样我们就有了循环队列的情况。
2.循环队列原理
(1)初始,数组整体为空时,队首front、队尾tail指向同一个位置(数组索引为0的地方)也即front==tail 时队列为空
(2)当往数组中添加元素后,
(3)出队一个元素,front指向新的位置
(4)入队元素,tail叠加
(5)当tail不能再增加时,数组前面还有空余,此时循环队列就该出场了。
此时数组应该变为这样:
在往数组中添加一个元素:
这样数组就已经满了(tail+1==front 队列满),开始出发扩容操作。【capacity中,浪费一个空间】。
为了tail能返回到数组的前面位置,将队列满的表达式变为 【(tail+1)%c==front】这样数组就可以循环移动了。
3.循环队列代码实现
新建一个类LoopQueue并实现接口Queue。
#1:接口Queue代码如下:
package Queue;
public interface Queue<E> {
//获取队列中元素个数
int getSize();
//队列中元素是否为空
boolean isEmpty();
//入队列
void enqueue(E e);
//出队列
E dequeue();
//获取队首元素
E getFront();
}
#2:LoopQueue相关代码:
package Queue;
//循环队列
public class LoopQueue<E> implements Queue<E> {
private E[] data;
private int front, tail;
private int size;//队列中元素个数
//构造函数,传入队列的容量capacity构造函数
public LoopQueue(int capacity) {
data = (E[]) new Object[capacity + 1];//浪费与一个空间
front = 0;
tail = 0;
size = 0;
}
//无参构造函数,默认队列的容量capacity=10
public LoopQueue() {
this(10);
}
//真正容量
public int getCapacity() {
return data.length - 1;
}
//队列是否为空
@Override
public boolean isEmpty() {
return front == tail;
}
//队列中元素个数
@Override
public int getSize() {
return size;
}
//入队列操作
@Override
public void enqueue(E e) {
if ((tail + 1) % data.length == front) {//队列已满,需要扩容
resize(getCapacity() * 2);
}
data[tail] = e;
tail = (tail + 1) % data.length;
size++;
}
//出队操作
@Override
public E dequeue() {
if (isEmpty()) {
throw new IllegalArgumentException("队列为空");
}
E ret = data[front];
data[front] = null;//手动释放
front = (front + 1) % data.length;
size--;
if (size == getCapacity() / 4 && getCapacity() / 2 != 0) {
resize(getCapacity() / 2);
}
return ret;
}
//获取队首元素
@Override
public E getFront() {
if (isEmpty()) {
throw new IllegalArgumentException("队列为空");
}
return data[front];
}
//改变容量
private void resize(int newCapacity) {
E[] newData = (E[]) new Object[newCapacity + 1];
for (int i = 0; i < size; i++) {
newData[i] = data[(front + i) % data.length];//循环数组防止越界
}
data = newData;
front = 0;
tail = size;
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append(String.format("Queue:size=%d, capacity=%d\n", size, getCapacity()));
res.append("front [");
for (int i = front; i != tail; i = (i + 1) % data.length) {
res.append(data[i]);
if ((i + 1) % data.length != tail) {
res.append(",");
}
}
res.append("] tail");
return res.toString();
}
}
在关于LoopQueue类中需要注意的:
(1)第11行中的+1是capacity需要浪费一个空间,故在实例化是多加1
data = (E[]) new Object[capacity + 1];//浪费与一个空间
(2)地24行真正的容量是data.length-1,这是由于有一个空间是浪费的。
data.length - 1;
(3)关于入队中第46行tail值的说明
为了保证入队是循环操作,tail值的变化规律为
tail = (tail + 1) % data.length;
(4)关于82行的数据迁移操作,取余操作是为了防止循环数组时越界。
newData[i] = data[(front + i) % data.length];//循环数组防止越界
#3直接在LoopQueue中添加一个main函数进行测试,相关代码如下:
//测试用例
public static void main(String[] args) {
LoopQueue<Integer> queue = new LoopQueue<Integer>();
for (int i = 0; i < 10; i++) {
queue.enqueue(i);
System.out.println(queue);
if(i%3==2){//每添加3个元素出队列一个
queue.dequeue();
System.out.println(queue);
}
}
}
结果为:
4.循环队列时间复杂度
到此我们就实现了一个循环队列操作,解决了在顺序队列中出队时的时间复杂度为O(n)的情况,在循环队列中出队的时间复杂度为O(1)。
源码地址 https://github.com/FelixBin/dataStructure/blob/master/src/Queue/LoopQueue.java
希望本文所述对大家java程序设计有所帮助。
来源:https://www.cnblogs.com/wfaceboss/p/10628345.html


猜你喜欢
- 零、关于HibernateHibernate是冬眠的意思,它是指动物的冬眠,但是本文讨论的Hibernate却与冬眠毫无关系,而是接下来要讨
- 前言最近测试反馈一个问题,某个查询全量信息的接口,有时候返回全量数据,符合预期,但是偶尔又只返回1条数据,简直就是“见鬼
- 讲解之前需要说明的是旋转屏幕:在系统的自动旋转屏幕开启的情况下,我们旋转屏幕手动设置屏幕:我们自己去调用Activity的 setReque
- 相信大家使用多点对图片进行缩放,平移的操作很熟悉了,大部分大图的浏览都具有此功能,有些app还可以对图片进行旋转操作,QQ的大图浏览就可以对
- 前言今天我们来讨论一下,程序中的错误处理。在任何一个稳定的程序中,都会有大量的代码在处理错误,有一些业务错误,我们可以通过主动检查判断来规避
- 导入项目集成环境:IntelliJ IDEA 2020.1.2演示系统:DELL Windows 10Eclipse项目如何导入IDEA并成
- 底座的状态跟充电状态类似,很多底座提供充电功能(座充).底座状态同样使用sticky Intent广播。可以查询设备是否插入底座,哪种底座。
- 我们平时在日常项目中经常会遇到图片的上传和访问的情景,平时我们可能习惯于把图片传到resource或者项项目中的某个位置,这样会有一个缺点,
- 问题描述这里我想测试某个与springboot相关的问题,结果在搭建mybatis时,发现没有成功从数据库中获取数据,报错信息为com.my
- 先看看效果图:开源项地址:https://github.com/chrisbanes/Android-PullToRefresh
- 在c#中可以遍历指定驱动器或指定目录下嵌套目录中的所有文件或者任意深度的文件。通过遍历可以检索string形式的目录名和文件名,也可以检索
- 代码如下import java.util.concurrent.Callable;import java.util.concurrent.E
- 单一职责原则:一个类,只有一个引起它变化的原因。为什么需要单一职责原则?如果一个类有多个原因要去修改它,那么修改一个功能时,可能会让其他功能
- 前言之前用简书的时候一直是在web端,后来下载了客户端,看到了搜索的那个动画,就尝试的去写了,没写之前感觉挺容易的,写了之后,就感觉里面还是
- Android onKeyDown监听返回键无效的解决办法当我们的Activity继承了TabActivity,在该类中重写on
- 项目背景我们开发过程中会碰到这样一类问题,就是数据层或三方接口返回的Bean对象需要转换重新装换一下我们需要的对象。我们通常的做法就是通过g
- 下面笔者说说自己对进制转换的分析:笔者认为,任何进制都可以直接转换到十进制,而十进制也可以相当容易的转换到其他进制,所以笔者在这里将十进制作
- 跨域配置如下,Springboot 版本为 2.4.1///跨域访问配置@Configurationpublic class CorsCon
- 这两天看到Hibernate的代理部分,第一反应是底层使用了反射,针对用户实体生成了代理类,后来反应过来了,反射没有任何可以产生新类的能力,
- 一、先来看看效果演示二、实现原理:这个其实不难实现,通过一个定时器不断调用TextView的setText就行了,在setText的时候播放