java循环1000000000_循环队列(Java)

队列(Queue)

是一种线性结构

只能从队尾添加元素,称为入队(Enqueue);

只能从队首取出元素,称为出队(Dequeue);

c4dcd2a41e8dedbd837a9e83a1ad6cfa.png

先进先出的特性(FIFO-first in first out):最先插入的元素最先出来.

back:当前队尾的索引(下面我们用tail代替)

front:当前队首的索引

普通数组队列的问题

取元素时只能从数组首端,也就是索引为0的位置取出

存放元素只能从数组末端添加,

而且队首到队尾之间的元素不能存在空闲,要一个紧挨着一个

但会存在一个缺陷,想象一下:

**如果从队首取出一个元素,那么索引为0的位置就空了出来,

必须把后继的所有元素遍历向前挪一个位置;**

这样操作时间复杂度为O(n):

n代表着数组中的元素个数,这个算法的运行时间与元素个数是一个线性的关系,也就是说nums中的元素个数越多,这个方法的运行时间就越长.

为了解决这个问题,需要抽象一下:

c26c9511a00b3f00b92084a9ee052941.png

循环队列的机制:

front:当前队首的索引

tail:当前队尾的索引

capcity:容量,由用户定义数组可容纳多少元素

把整个数组想象成一个首尾相连的闭环,队首元素出队后不再挪动其他元素,

而是维护一个front的变量表示当前队首的位置;

当数组末端位置占满后,新入队的元素添加到数组前端空虚的位置,

维护一个tail的变量表示当前队尾的位置,tail = 当前末端索引+1 % arr.length;

但是注意当这个闭环要满的时候,最后一个位置也就是tail所指的位置不能再插入元素了,

防止front == tail,此时就应该扩容,所以获取容量的时候应该忽略这个空闲的位置capacity = arr.length -1

设计了以上措施后front == tail的情况只能是队列为空

(tail + 1) % arr.length == front 表示队列已满

如此,dequeue()的复杂度就成了O(1) (均摊)

下面是具体代码,包含扩容机制和时间复杂度测试:

定义接口,使用泛型,让它可以存储任意类型的数据.

public interface Queue {

//获取有效的元素个数

int getSize();

//查看队列是否为空

boolean isEmpty();

//入队

void enqueue(E e);

//出队

E dequeue();

//查看队首的元素

E getFront();

}

实现类:

public class LoopQueue implements Queue {

//队列元素

private E[] data;

//队首, 队尾的位置

private int front, tail;

private int size;

//根据用户指定的容量初始化队列

public LoopQueue(int capacity){

//需要预留出一个空间

data = (E[])new Object[capacity + 1];

//初始化成员变量

front = 0;

tail = 0;

size = 0;

}

//无参构造

public LoopQueue(){

//调用有参构造,设置默认容量为10

this(10);

}

//获取队列的容量

public int getCapacity(){

//在数组中有一个空间始终会被空闲

return data.length - 1;

}

//获取有效元素的个数

@Override

public int getSize() {

return size;

}

//查看队列是否为空

@Override

public boolean isEmpty() {

return front == tail;

}

//向队列尾部添加元素(入队)

@Override

public void enqueue(E e) {

//判断队列是否已满

if ((tail + 1) % data.length == front)

//扩容为当前容量的2倍

resize(getCapacity() * 2);

data[tail] = e;

//维护tail值

tail = (tail + 1) % data.length;

size ++;

}

private void resize(int newCapacity){

//仍然需要预留一个空间

E[] newData = (E[]) new Object[newCapacity + 1];

//将元素复制到新的数组中

for (int i = 0; i < size; i++)

// data中的元素相对于newData中的元素,

// 索引值是存在front这么多的偏移的(没懂)

// % data.length是为了防止越界

newData[i] = data[(i + front) % data.length];

data = newData;//data指向新的数组

front = 0;//队首又回到了0号索引

tail = size;//对尾便是size

}

//队首元素出队操作

@Override

public E dequeue() {

//如果队列是空的,抛出异常

if (isEmpty())

throw new IllegalArgumentException("Cannot dequeue from an empty queue.");

//保存出队的元素

E returnElement = data[front];

//将它的引用变为null,垃圾回收器就会回收这片空间

data[front] = null;

//维护front值

front = (front + 1) % data.length;

size --;

// 如果当前数组的容量只有1/4被利用到,就进行缩容

// Lazy的思想,延迟缩容,防止复杂度震荡

if (size == getCapacity() / 4 && getCapacity() / 2 != 0)

resize(getCapacity() / 2);

return returnElement;

}

//查看队首的元素

@Override

public E getFront() {

//如果队列是空的,抛出异常

if (isEmpty())

throw new IllegalArgumentException("Cannot dequeue from an empty queue.");

//返回front位置的元素即可

return data[front];

}

@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();

}

}

//测试用例

public static void main(String[] args) {

LoopQueue queue = new LoopQueue<>();

for (int i = 0; i < 10; i++){

//将0~9的数字入队

queue.enqueue(i);

System.out.println(queue);

//每隔3个数字,做一次出队操作

if (i % 3 == 2){

queue.dequeue();

System.out.println(queue);

}

}

}

--------console---------

Queue: size = 1 , capacity = 10

front [0] tail

Queue: size = 2 , capacity = 10

front [0, 1] tail

Queue: size = 3 , capacity = 10 //触发缩容

front [0, 1, 2] tail

Queue: size = 2 , capacity = 5

front [1, 2] tail

Queue: size = 3 , capacity = 5

front [1, 2, 3] tail

Queue: size = 4 , capacity = 5

front [1, 2, 3, 4] tail

Queue: size = 5 , capacity = 5

front [1, 2, 3, 4, 5] tail

Queue: size = 4 , capacity = 5

front [2, 3, 4, 5] tail

Queue: size = 5 , capacity = 5

front [2, 3, 4, 5, 6] tail

Queue: size = 6 , capacity = 10 //触发扩容

front [2, 3, 4, 5, 6, 7] tail

Queue: size = 7 , capacity = 10

front [2, 3, 4, 5, 6, 7, 8] tail

Queue: size = 6 , capacity = 10

front [3, 4, 5, 6, 7, 8] tail

Queue: size = 7 , capacity = 10

front [3, 4, 5, 6, 7, 8, 9] tail

与普通数组队列的执行时间对比:

public class Main {

//测试使用q运行opCount个enqueue和dequeue操作所需要的时间,单位:秒

private static double testQueue(Queue q,int opCount){

long startTime = System.nanoTime();

Random random = new Random();

for (int i = 0; i < opCount; i++)

//生成0 ~ Integer最大值之间的随机数入队

q.enqueue(random.nextInt(Integer.MAX_VALUE));

for (int i = 0; i < opCount; i++)

q.dequeue();

long endTime = System.nanoTime();

return (endTime - startTime) / 1000000000.0;

}

public static void main(String[] args) {

int opCount = 100000;

ArrayQueue arrayQueue = new ArrayQueue<>();

double time1 = testQueue(arrayQueue,opCount);

System.out.println("ArrayQueue, time:" + time1 + " s");

LoopQueue loopQueue = new LoopQueue<>();

double time2 = testQueue(loopQueue, opCount);

System.out.println("LoopQueue, time:" + time2 + " s");

}

}

-----------console------------

ArrayQueue, time:3.306405 s

LoopQueue, time:0.0124581 s //优化了太多妙啊

结合自己的理解记录了学习的过程,希望对你有帮助

图片来自网络

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值