我们之前学习了队列,但是它的时间复杂度比不是很理想,我们就再次学习循环队列和双端队列。
队列的顺序存储结构本身是由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[]) new Object[newlLen];
int index = 0;
for (int i = front;i != rear;i = (i+1)%data.length){
newData[index++] = data[i];
}
data = newData;
front = 0;
rear = index;
}
接下来就是查看队首元素,判空,清除,求有效元素的操作。
查看队首元素即需要判空操作,如果为空则抛出异常,否则直接返回头角标处的元素。
判空操作则是返回判断头角标与尾角标是否相同,如过相同则为空。如果不同则为不空。
清楚对内所有元素则是暴力解决,重新创建容器,头角标,尾角标,有效元素个数都为0;
求有效元素个数即直接返回size的值即可。
代码如下
@Override
public E element() {
if (isEmpty()){
throw new IllegalArgumentException("queue is null");
}
return data[front];
}
@Override
public boolean isEmpty() {
return front == rear;
}
@Override
public void clear() {
data = (E[]) new Object[DEFAULT_CAPACITY];
size = 0;
front = 0;
rear = 0;
}
@Override
public int size() {
return size;
}
还有toString(),Iterator(),equals()函数。
对于toString(),老规矩使用StringBuilder.首先添加'[',然后我们还是进行判空操作,如过为空则直接添加']' 返回即可。如果不为空,去遍历容器中的元素,从头角标开始,如果遍历到尾下表则结束,遍历索引是通过(i+1)y%data.length去增加的。不断添加元素,然后遍历到(i+1)%data.length == rear时候直接添加]']'即可;最后返回最终结果;
对于equals()函数,我们首先判断是否为空,如果为空则直接返回false;然后如果和自己比较相同,返回true;再就是判单类型是否相同直接返回false,若为同类型,再去创建一个ArrayLoopQueue对象,判断两个循环队列的有效元素是否相同,若队首角标与队尾角标不相同,则就去循环遍历每一个元素进行比较。注意两个的队列进行遍历的时候索引为(i+1)%data.length。
最后就是迭代器Iterator()函数,然会自己的迭代器对象,重写hasNext()函数和next()函数;hasNext()里面判断下一个角标是否有元素,若没有就不去遍历,如有就执行next()函数;next()是返回此处的元素并且角标指向下一个。hasNext()函数判断的条件就是索引与尾角标是否相等,若相等则不再去遍历,若不同就继续去遍历。还是主要原因为尾角标指的是最后一个有效元素的后面一个(空)。索引移动的时候也是cur = (cur+1)%data.length变化;
代码如下
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
if (isEmpty()){
sb.append(']');
return sb.toString();
}
for (int i = front;i != rear;i=(i+1)%data.length){
sb.append(data[i]);
if ((i + 1)%data.length == rear) {
sb.append(']');
}else {
sb.append(',');
sb.append(' ');
}
}
return sb.toString();
}
@Override
public boolean equals(Object o) {
if (o == null ){
return false;
}
if (this == o ){
return true;
}
if (o instanceof ArrayLoopQueue){
ArrayLoopQueue<E> other = (ArrayLoopQueue<E>) o;
if (size != other.size){
return false;
}
int i = front;
int j = other.front;
while (i != rear){
if (!data[i].equals(other.data[j])){
return false;
}
i = (i+1) % data.length;
j = (j+1) % data.length;
}
return true;
}
return false;
}
@Override
public Iterator iterator() {
return new ArrayLoopQueueIterator();
}
class ArrayLoopQueueIterator implements Iterator<E>{
private int cur = front;
@Override
public boolean hasNext() {
return cur != rear;
}
@Override
public E next() {
E ret = data[cur];
cur = (cur + 1 )%data.length;
return null;
}
}
循环队列事先类完整代码
package p2.线性结构;
import p1.接口.Queue;
import java.util.Iterator;
//循环队列
public class ArrayLoopQueue<E> implements Queue<E>{
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;
}
private void resize(int newlLen) {
E[] newData = (E[]) new Object[newlLen];
int index = 0;
for (int i = front;i != rear;i = (i+1)%data.length){
newData[index++] = data[i];
}
data = newData;
front = 0;
rear = index;
}
@Override
public E element() {
if (isEmpty()){
throw new IllegalArgumentException("queue is null");
}
return data[front];
}
@Override
public boolean isEmpty() {
return front == rear;
}
@Override
public void clear() {
data = (E[]) new Object[DEFAULT_CAPACITY];
size = 0;
front = 0;
rear = 0;
}
@Override
public int size() {
return size;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
if (isEmpty()){
sb.append(']');
return sb.toString();
}
for (int i = front;i != rear;i=(i+1)%data.length){
sb.append(data[i]);
if ((i + 1)%data.length == rear) {
sb.append(']');
}else {
sb.append(',');
sb.append(' ');
}
}
return sb.toString();
}
@Override
public boolean equals(Object o) {
if (o == null ){
return false;
}
if (this == o ){
return true;
}
if (o instanceof ArrayLoopQueue){
ArrayLoopQueue<E> other = (ArrayLoopQueue<E>) o;
if (size != other.size){
return false;
}
int i = front;
int j = other.front;
while (i != rear){
if (!data[i].equals(other.data[j])){
return false;
}
i = (i+1) % data.length;
j = (j+1) % data.length;
}
return true;
}
return false;
}
@Override
public Iterator iterator() {
return new ArrayLoopQueueIterator();
}
class ArrayLoopQueueIterator implements Iterator<E>{
private int cur = front;
@Override
public boolean hasNext() {
return cur != rear;
}
@Override
public E next() {
E ret = data[cur];
cur = (cur + 1 )%data.length;
return null;
}
}
}
我们在写一个测试类;
package p0.测试;
import p2.线性结构.ArrayDeque;
import p2.线性结构.ArrayLoopQueue;
public class TestArrayQueue {
public static void main(String[] args) {
ArrayLoopQueue<Integer> queue = new ArrayLoopQueue<>();
for (int i = 1;i<=10;i++){
queue.offer(i);
}
System.out.println(queue);
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue);
}
}
运行结果
双端队列
由于双端队列是基于循环队列的,在其上面增加了在队首入队和队尾出队操作,但不可以中间操作。所以我们就重写接口,实现这个类。我们就直接上代码吧;如果能看懂上面的,耐这个就比较容易啦。现在已经是深夜了,有点扛不住就不一 一去解释了。上代码!
完整代码
双端队列接口代码
package p1.接口;
public interface Deque<E> extends Queue<E> {
public void addFirst(E element);
public void addLast(E element);
public E removeFirst();
public E removeLast();
public E getFirst();
public E getLast();
}
双端队列实现类代码
package p2.线性结构;
import p1.接口.Deque;
import p1.接口.Stack;
import java.util.Arrays;
import java.util.Iterator;
public class ArrayDeque<E> implements Deque<E>, Stack<E> {
private E[] data;
private int front;
private int rear;
private int size;
private static int DEFAULT_CAPACITY = 10;
public ArrayDeque() {
data = (E[]) new Object[DEFAULT_CAPACITY + 1];
front = 0;
rear = 0;
size = 0;
}
@Override
public void addFirst(E element) {
if ((rear + 1) % data.length == front) {
resize(data.length * 2 - 1);
}
front = (front - 1 + data.length) % data.length;
data[front] = element;
size++;
}
private void resize(int newLen) {
E[] newData = (E[]) new Object[newLen];
int index = 0;
for (int i = front; i != rear; i = (i + 1) % data.length) {
newData[index++] = data[i];
}
data = newData;
front = 0;
rear = index;
}
@Override
public void addLast(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 removeFirst() {
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 null;
}
@Override
public E removeLast() {
if (isEmpty()) {
throw new IllegalArgumentException("queue is null");
}
rear = (rear - 1 + data.length) % data.length;
E ret = data[rear];
size--;
if (size <= (data.length - 1) / 4 && data.length - 1 > DEFAULT_CAPACITY) {
resize(data.length / 2 + 1);
}
return ret;
}
@Override
public E getFirst() {
if (isEmpty()) {
throw new IllegalArgumentException("queue is null");
}
return data[front];
}
@Override
public E getLast() {
if (isEmpty()) {
throw new IllegalArgumentException("queue is null");
}
return data[(rear - 1 + data.length) % data.length];
}
@Override
public void offer(E element) {
addLast(element);
}
@Override
public E poll() {
return removeFirst();
}
@Override
public E element() {
return getFirst();
}
@Override
public E peek() {
return getLast();
}
@Override
public boolean isEmpty() {
return size == 0 && front == rear;
}
@Override
public void push(E element) {
addLast(element);
}
@Override
public E pop() {
return removeLast();
}
@Override
public void clear() {
E[] data = (E[]) new Object[DEFAULT_CAPACITY];
front = 0;
rear = 0;
size = 0;
}
@Override
public int size() {
return size;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append('[');
if (isEmpty()) {
sb.append(']');
return sb.toString();
}
for (int i = front; i != rear; i = (i + 1) % data.length) {
sb.append(data[i]);
if ((i + 1) % data.length == rear) {
sb.append(']');
} else {
sb.append(',');
sb.append(' ');
}
}
return sb.toString();
}
@Override
public Iterator<E> iterator() {
return new ArrayDequeIterator();
}
class ArrayDequeIterator implements Iterator<E> {
private int cur = front;
@Override
public boolean hasNext() {
return cur != rear;
}
@Override
public E next() {
E ret = data[cur];
cur = (cur + 1) % data.length;
return ret;
}
}
}
测试类
import p2.线性结构.ArrayDeque;
import p2.线性结构.ArrayLoopQueue;
public class TestArrayQueue {
public static void main(String[] args) {
ArrayDeque<Integer> queue = new ArrayDeque<>();
for (int i = 1;i<=10;i++){
queue.offer(i);
}
System.out.println(queue);
queue.poll();
System.out.println(queue);
}
}
我只测试了一两个方法,剩余的大家去测试一下。