3 队列Queue
3.1 基本概念
- 定义:
队列与 栈 一样,都是操作受限的线性表数据结构。队列从一端插入数据,然后从另一端取出数据。插入数据的一端称为” 队尾 “,取出数据的一端称为” 队头 “; - 特点:FIFO
队列是一种先进先出的数据结构(先到先得);
3.2 队列的实现
public interface Queue<E>{
void enqueue(E e);
int getSize();
boolean isEmpty();
E dequeue();
E getFront();
}
- 需要实现的方法:
- 入队、出队
- 获取队列内元素数量
- 判断队列是否为空
- 获取队首元素
3.2.1 顺序队列
- 动态数组实现
在队列尾部没有空间时,需要将数据整个搬移,完成数组容量的更新。
public class ArrayQueue<E> implements Queue<E>{
private Array<E> array;
public ArrayQueue(int capacity){
array=new Array<>(capacity);
}
public ArrayQueue(){
array=new Array<>();
}
@Override
public int getSize(){
return array.getSize();
}
@Override
public boolean isEmpty(){
return array.isEmpty();
}
public int getCapacity(){
return array.getCapacity();
}
@Override
public void enqueue(E e){
array.addLast(e);
}
@Override
public E dequeue(){
return array.removeFirst();
}
@Override
public E getFront(){
return array.getFirst();
}
@Override
public String toString(){
StringBuilder res=new StringBuilder();
res.append("Queue:");
res.append("front[");
for(int i=0;i< array.getSize();i++){
res.append(array.get(i));
if(i!=array.getSize()-1){
res.append(",");
}
}
res.append("]tail");
return res.toString();
}
public static void main(String[] args){
ArrayQueue<Integer> queue=new ArrayQueue<>();
for(int i=0;i<10;i++){
queue.enqueue(i);
System.out.println(queue);
if(i%3==2){
queue.dequeue();
System.out.println(queue);
}
}
}
}
- 循环队列
-
队列中没有元素时:
front==tail 队列为空 -
队列中入队一个元素:tail++
-
队列中出队一个元素:front++
-
循环的意义:
(tail+1)%capacity -
队列满:
- front=tail与队列为空条件相同,不便于判断
- 在capacity中浪费一个空间,使得(tail+1)%c==front为队列满的条件
实现代码:
public class LoopQueue<E> implements Queue<E>{
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(){
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(2*getCapacity());
data[tail]=e;
tail=(tail+1)% data.length;
size++;
}
@Override
public E dequeue(){
if(isEmpty())
throw new IllegalArgumentException("Cannot dequeue from an empty queue");
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("Cannot getFront from an empty queue");
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[(i+front)%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(", ");
// }
for(int i=0;i<size;i++){
res.append(data[(i+front)% data.length]);
if((i+front+1)%data.length!=tail)
res.append(",");
}
res.append("]tail\n");
res.append("data.length:"+data.length+' ');
res.append("size:"+size);
return res.toString();
}
public static void main(String[] args){
LoopQueue<Integer> queue=new LoopQueue<>();
for(int i=0;i<10;i++){
queue.enqueue(i);
System.out.println(queue);
if(i%3==2){
queue.dequeue();
System.out.println(queue);
}
}
}
}
3.2.2 循环队列与数组队列的对比
- 时间复杂度分析:
- 数组队列
由于动态数组实现的队列中,入队操作只需要往数组末尾添加元素,所以入队的时间复杂度为O(1);出队操作对应的是将数组第一个元素删除,需要将后面的元素全部往前移动一个位置,所以时间复杂度为O(n); - 循环队列
循环队列中入队、出队操作都不涉及元素的移动。只有front、tail索引发生+1-1的改变,在数组容量发生变化时均摊复杂度仍为O(1);所以循环队列的各种操作时间复杂度为O(1);
- 实验验证:
import java.util.Random;
public class QueueTest {
private static double testQueue(Queue<Integer> q,int opCount){
long startTime=System.nanoTime();
Random radom=new Random();
for(int i=0;i<opCount;i++){
q.enqueue(radom.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<Integer> arrayQueue=new ArrayQueue<>();
double time1=testQueue(arrayQueue,opCount);
System.out.println("ArrayQueue time:"+time1+'s');
LoopQueue<Integer> loopQueue=new LoopQueue<>();
double time2=testQueue(loopQueue,opCount);
System.out.println("LoopQueue time:"+time2+'s');
}
}
输出结果为:
证明了循环队列的性能要优于普通的数组队列,尤其是在出队速度上循环队列较快。
3.2.2 链表队列
package DataStructure;
public class LinkedListQueue<E> implements Queue<E>{
private class Node{
public E e;
public Node next;
public Node(E e,Node next){
this.e=e;
this.next=next;
}
public Node(E e){
this(e,null);
}
public Node(){
this(null,null);
}
@Override
public String toString(){
return e.toString();
}
}
private Node head;
private Node tail;
private int size;
public LinkedListQueue(){
head=null;
tail=null;
size=0;
}
@Override
public int getSize(){
return size;
}
@Override
public boolean isEmpty(){
return size==0;
}
@Override
public void enqueue(E e){
if(tail==null){
tail=new Node(e);
head=tail;
}else{
tail.next=new Node(e);
tail=tail.next;
}
size++;
}
@Override
public E dequeue(){
if(isEmpty())
throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
Node retNode = head;
head = head.next;
retNode.next = null;
if(head == null)
tail = null;
size --;
return retNode.e;
}
@Override
public E getFront(){
if(isEmpty())
throw new IllegalArgumentException("GetFront failed,can not getFront from an empty queue");
return head.e;
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append("Queue: front ");
Node cur = head;
while(cur != null) {
res.append(cur + "->");
cur = cur.next;
}
res.append("NULL tail");
return res.toString();
}
public static void main(String[] args){
LinkedListQueue<Integer> queue = new LinkedListQueue<>();
for(int i = 0 ; i < 10 ; i ++){
queue.enqueue(i);
System.out.println(queue);
if(i % 3 == 2){
queue.dequeue();
System.out.println(queue);
}
}
}
}