我们之前学习了队列,但是它的时间复杂度比不是很理想,我们就再次学习循环队列和双端队列。
队列的顺序存储结构本身是由ArrayList实现的 在数据元素入队的时候,相当于在ArrayList表尾添加元素 在数据元素出队的时候,相当于在ArrayList表头删除元素 很明显,入队的时间复杂度O(1),出队的时间复杂度O(n) 线性表增删数据元素时间复杂符都是O(n),但是这个是按平均算的 队列的出队时间复杂度O(n),可不是按平均算的,因为每次出队都是O(n)。
我们不断的进行优化,最终得到一个循环队列。队列满的条件 (Rear+1)%n==Front 队列空的条件 Rear==Front;这样就可以解决时间复杂度的问题了。那接下来我们就开始进行学习!
首先去定义个容器 在定义队首角标,队尾角标,有效元素个数(如果队首角标在队尾角标的左边,则有效元素的个数是尾角标减去头角标,若队首角标在尾角标的右边,则是尾角标加1减去头角标.)然后去定义默认容量。再去写一个构造函数。
代码如下
private E[] data;
private int front;//队首指针(角标索引)
private int rear;//队尾指针(角标索引)
//有效元素个数(f < r r - f;r < f-> r + l -f)
private int size;
private static int DEFAULT_CAPACITY = 10;
public ArrayLoopQueue(){
data = (E[])new Object[DEFAULT_CAPACITY + 1];
front = 0;
rear = 0;
size = 0;
}
我们这个类实现了队列的接口。所以得重写接口里面的所有方法。
首先我们先写入队出队函数。入队是从后面进入。首先我们进行判断对列是否为满。这里我们有两种判断方法用有效个数和索引。我们在这里使用索引进行判满。如果是满的,我们需要扩容。所以在这里我们还得定义一个扩容函数(其实扩容和缩容函数逻辑是相同,只是传入的参数不同。我们稍后讲解)。如果不是满的,我们直接让新的元素放在尾索引处,然后尾索引进行后移。在这里为尾索引并不是直接加一操作,因为我们在这是循环队列,所以尾索引应该是加一对容器长度进行取余,如果一直加一会出现索引越界问题。然后有效个数加一即可。
对于出队函数,我们首先是判空操作,和上述入队是一样的。如果不是空,则我们要让队首元素进行出队操作,把头元素暂存到临时变量中,头索引应该向右移动。向右移也不是直接加一,原因和入队操作是一样的。然后有效元素个数减一就可以。在出队的时候得考虑缩容为题,如果有效元素鹅个数小于容器长度的四分之并且有效元素个数大于默认容量,则需要缩容。
代码如下
@Override
public void offer(E element) {
//满了没
if ((rear + 1) % data.length == front ){
resize(data.length * 2 -1);
}
data[rear] = element;
rear = (rear + 1) % data.length;
size++;
}
@Override
public E poll() {
//是否为空
if (isEmpty()){
throw new IllegalArgumentException("queue is null");
}
E ret = data[front];
front = (front + 1)%data.length;
size--;
if (size <= (data.length - 1) / 4 && data.length - 1>DEFAULT_CAPACITY){
resize(data.length / 2 + 1);
}
return ret;
}
上述我们讲到扩容函数和缩容函数,我们在这里讲解一下!
如果是扩容我们得重新创建一个容器,旧容器的头索处的元素应该放在新容器的索引为0处,此次遍历旧容器,遍历开始是在头角标处,如果遍历到尾角标处则就停止,因为尾角标处按java思想是没有元素的。角标的增长是角标加一再去对容器长度取余,然后挨个把元素赋值给新容器每一个索引处,索引是先给之值再去加[ newdata[index++] = data[i];最后将旧容器指向新容器,头角标设置为0,尾角标设置为indexjike。缩容问题就是反过来,实现操作是相同的。只是穿进来的参数是不同的。扩容的时候是传入容器长度二倍减一,减一是因为容器内尾角标指向的是空,若不减一,则会有两个空。缩容的时候是容器长度除二减一,减一操作与扩容原理相同。
代码如下
private void resize(int newlLen) {
E[] newData = (E[