队列
队列是只允许在一端进行插入操作而在另一端进行删除操作的线性表 允许插入的一端为队尾,允许插入的一端为队头(注:队列是一种先进先出线性表)
队列的基本操作是入队和出队;入队在表的末端插入一个元素,出队删除表头的元素并返回。
队列的数组实现:
对于数组的实现首先需要一个头指针,尾指针。一个数组,和数组中元素的个数。表示队列的状态。
队列的顺序存储结构 —入队操作O(1)
当队列为空时,队头和队尾的指针指向同一个位置。
队列的操作
对列的抽象数据类型
Queue
Data 元素具有相统的类型。并且相邻元素之间有前驱和后继关系(队头和队尾外)
创建空队列;
销毁队列;
请空队列;
队列是否为空;
出队操作;
入队操作
队列的长度。
队列接口的实现
public interface Queue<E> {
public int getSize();
public boolean isEmpty();
public void clear();
/**
* 入队一个新元素e
* @param e
*/
public void enqueue(E e);
/**
* 出队一个元素e
* @return
*/
public E dequeue();
/**
* 得到队头元素
* @return
*/
public E getFront();
/**
* 得到队尾元素
* @return
*/
public E getRear();
}
队列的实现:
//队列
import com.openlab.line.ArrayList;
public class ArrayQueue<E> implements Queue<E> {
private ArrayList<E> list;
public ArrayQueue() {
list = new ArrayList<E>();
}
public ArrayQueue(int capacity) {
list = new ArrayList<E>(capacity);
}
@Override
public int getSize() {
return list.getSize();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public void clear() {
list.clear();
}
@Override
public void enqueue(E e) {
list.addLast(e);
}
@Override
public E dequeue() {
return list.removeFrist();
}
@Override
public E getFront() {
return list.getFrist();
}
@Override
public E getRear() {
return list.getLast();
}
}
队列顺序存储结构的弊端:
队列的顺序存储结构本身是由ArrayList实现的
在数据元素入队时,相当于在ArrayList的表尾添加元素
在数据元素出队时,相当于在ArrayList的表头删除元素
很明显入队的时间复杂度o(1)出队的时间复杂对为o(n)
线性表增删数据元素时间复杂度都是o(n),但这是按平均算的,队列的出队时间复杂度O(n),可不是按
平均算的,因为每次出队都是o(n)
因此要对队列进行优化:如果要对队列中的空间充分的利用当队尾指针指到最后一个位置的时候,将他移到表头。进行循环。于是出现了循环队列
循环队列
优化第一步:
能否让队头指针和队尾指针一样随着元素的变化而移动。
入队一个元素队尾向后移动一位。出队一个元素队头向后以一个位置。
出队和入队的操作时间复杂度为o(1)
当一定情况下尾指针不能再继续后移了。
但队列长度一直增加,一直向后移动会造成队列前段的空间被浪费。
优化第二步:
当队尾或队头指针到达尾部时,如果后移可以重新指向表头。(解决空间浪费)
如何判断队列已满?如何判断队列为空?
队列满的条件(Rear+1)%nFront;
队列为空 rear==front;
当头指针和尾指针指向同一个位置;
优化第三步:
将一个空间预留出来不存任何元素,尾指针始终指向这个null空间
循环队列的定义:把这种头尾相接的顺序存储结构成为循环队列。
队尾指针rear指的位置永远为空。
当rearfront时队列为空
当(reae+1)%队列长度front时队列为满。
循环队列的实现:
循环队列也应该实现队列接口(也是队列)
public class ArrayQueueLoop<E> implements Queue<E> {
private E[] data;
private int front;
private int rear;
private int size;
public static int DEFAULT_SIZE = 10;
public ArrayQueueLoop() {
this(DEFAULT_SIZE);
}
public ArrayQueueLoop(int capacity) {
data = (E[]) new Object[capacity + 1];
front = 0;
rear = 0;
size = 0;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return front == rear && size == 0;
}
@Override
public void clear() {
size = 0;
front = 0;
rear = 0;
}
@Override
public void enqueue(E e) {
if ((rear + 1) % data.length == front) {
// 扩容
resize(data.length * 2 - 1);
}
data[rear] = e;
rear = (rear + 1) % data.length;
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];
}
front = 0;
rear = index;
data = newData;
}
@Override
public E dequeue() {
if (isEmpty()) {
throw new NullPointerException("队列为空");
}
E e = data[front];
front = (front + 1) % data.length;
size--;
if (size < data.length / 4 && data.length > DEFAULT_SIZE) {
resize(data.length / 2 + 1);
}
return e;
}
@Override
public E getFront() {
return data[front];
}
@Override
public E getRear() {
return data[(rear + data.length) % data.length];
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("ArrayQueueLoop:size="+getSize()+",capacity="+(data.length-1)+"\n");
if (isEmpty()) {
sb.append("[]");
}else {
sb.append('[');
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(',');
}
}
}
return sb.toString();
}
}
测试类:
public class main {
public static void main(String[] args) {
ArrayQueueLoop<Integer> queue = new ArrayQueueLoop<Integer>();
for (int i = 1; i <= 15; i++) {
queue.enqueue(i);
}
System.out.println(queue);
for (int i = 1; i < 10; i++) {
queue.dequeue();
}
System.out.println(queue);
System.out.println(queue.getFront());
System.out.println(queue.getRear());
}
}
结果
ArrayQueueLoop:size=15,capacity=20
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
ArrayQueueLoop:size=6,capacity=20
[10,11,12,13,14,15]
10
在循环队列中要明确头尾指针的位置。在循环队列扩容的时候要看准头尾指针。扩容从头指针开始将元素放到新数组中并且不能等于尾指针(尾指针永远指向空)再循环中i的长度应该是从头指针到(i+1)数组的长度。(当头尾指针不在最后一个位置时要考虑)
for (int i = front; i != rear; i = (i + 1) % data.length) {
newData[index++] = data[i];
}
队列的链式存储结构及实现。
队列的链式存储结构,其实就是线性表的单链表,只不过他只能尾进头出。(链队列)当队列为空时头指针和尾指针指向同一位置。
链队列入队其实就是在尾部插入节点。
出队就是头结点的后继出队。