以前总结过栈相关的知识,队列能够类比栈来看。栈只能在一端进行操做(栈顶),添加元素或者删除等都只能在栈顶;而队列有两端能够操做,在一端添加元素,在另外一端删除元素。html
咱们把添加元素的一端称为队尾;删除元素的一端称为队首。java
好比生活中的排队:城市中基本哪里都有,这就是一个队列。在队伍最前面就是队首,也是最早完成离开队伍的。新来的只能在队尾加入。数组
用链表、数组都比较容易实现队列。this
链表实现:比较简单,在链表首部删除,链表尾部插入便可。
链表没有长度限制,不会形成溢出,并且插入删除也很简单,是很适合的实现方式。code
数组实现:普通数组方式,有着明显的缺陷,长度固定、出队时剩下全部元素都要向前挪动一位,这种出队方式时间复杂度就是O(n)了。在此基础上进行一些处理,造成一个环,成为一个循环队列。htm
因此下面主要介绍的就是两种队列实现,用链表实现的队列 和 用数组实现的循环队列。blog
队列主要两个操做,入队和出队。
入队(enQueue): 即在队尾插入新元素。
出队(deQueue): 即在队首删除一个元素。
若是本身实现,能够添加更多公共操做,如队长、是否队空或队满、查找等等。队列
下面实现中,为了更直观的显示出队、入队操做,都添加了一个displayQueue()的方法。ci
单链表队列实现很简单,链表首部即队首,链表尾部即队尾,定义两个引用分别指向队首和队尾,出队、入队直接删除和插入便可。时间复杂度为O(1)。
优点,没有额外的操做,没有长度的限制 不需担忧溢出。get
示意图大体以下:
具体代码以下(可结合注释,应该比较清晰):
public class QueueLinkedList<E> { public static void main(String[] args) { //入队1,2,3,4,5;而后出队2次 QueueLinkedList<Integer> queueLinkedList = new QueueLinkedList<Integer>(); queueLinkedList.displayQueue(); for (int i = 1; i < 6; i++) { queueLinkedList.enQueue(Integer.valueOf(i)); } queueLinkedList.displayQueue(); queueLinkedList.deQueue(); queueLinkedList.displayQueue(); queueLinkedList.deQueue(); queueLinkedList.displayQueue(); } //定义队首引用,指向链表首部 private QueueNode<E> front; //定义队尾引用,指向链表尾部 private QueueNode<E> rear; //节点定义 static class QueueNode<E> { E data; QueueNode<E> next; public QueueNode(E data) { this.data = data; } } //初始化,空的队列。 public QueueLinkedList() { this.front = this.rear = null; } //入队,链表尾部插入 public void enQueue(E value) { QueueNode<E> newNode = new QueueNode<E>(value); //空队列时 if (this.rear == null) { this.front = this.rear = newNode; return; } this.rear.next = newNode; this.rear = newNode; } //出队,链表首部删除 public E deQueue() { //队列为空时 if (this.front == null) return null; E result = this.front.data; this.front = this.front.next; //队列中只有一个元素时 if (this.front == null) this.rear = null; return result; } //打印队列全部元素,以及队首、队尾信息 public void displayQueue() { if (this.front == null) { System.out.println("front:null<-...<-rear:null"); System.out.println("This is an empty queue!"); System.out.println(); return; } System.out.println("front:" + this.front.data +"<-...<-rear:" + this.rear.data); QueueNode<E> tmpNode = this.front; while (tmpNode != null) { System.out.print(tmpNode.data + "<-"); tmpNode = tmpNode.next; } System.out.println(); System.out.println(); } }
实验结果为:
front:null<-...<-rear:null This is an empty queue! front:1<-...<-rear:5 1<-2<-3<-4<-5<- front:2<-...<-rear:5 2<-3<-4<-5<- front:3<-...<-rear:5 3<-4<-5<-
若用普通数组实现队列,就会发现每次删除队首时全部元素都须要向前移动一位,这样实现确定是不合适的。
因而,在上述基础上作一些处理,便造成了循环队列:将数组队列看出一个首尾相连的环形。用front和rear表示队首和队尾相关元素的下标(不必定是front是队首下标,rear是队尾下表),这样就能很容易肯定队列的元素是哪些了。出队、入队改变这两个值便可而不须要移动数组中元素了。
下面实现用变量front和rear标识队首、队尾的下标,入队和出队的方式基本一致:数组大小为capacity,即front=(font+1)%capacity 或 rear=(rear+1)%capacity。
也能够用front标识队首,rear标识队尾的下一个位置,只是不一样的标识含义, 初始化 和 队列为空 及 队列为满 的判断和处理须要注意,是不一样的。 (rear标识队尾下一个位置时,初始化front=rear=0,空即front==rear。而rear标识队尾即下面的实现,能够具体看下)
大体示意图以下(front和rear标识队首、队尾的下标):
实现代码(可结合注释查看):
注:实现中的判空与判满 使用了另外一个变量queueCount表示队列元素个数,便于返回队列大小。也可使用front和rear之间关系进行判断
public class QueueCircleArray { public static void main(String[] args) { //队列入队1,2,3,4;而后两次出队;而后入队10,20 QueueCircleArray queueCircleArray = new QueueCircleArray(4); queueCircleArray.displayQueue(); for (int i = 1; i < 5; i++) { queueCircleArray.enQueue(Integer.valueOf(i)); } queueCircleArray.displayQueue(); queueCircleArray.deQueue(); queueCircleArray.deQueue(); queueCircleArray.displayQueue(); queueCircleArray.enQueue(Integer.valueOf(10)); queueCircleArray.enQueue(Integer.valueOf(20)); queueCircleArray.displayQueue(); } //数组及大小 private Object[] array; private int capacity; //队首 队尾对应数组中的下标 private int front, rear; //队列的大小 private int queueCount; public QueueCircleArray() { this(10); } public QueueCircleArray(int capacity) { this.capacity = capacity; this.front = queueCount = 0; //注意这里初始化 this.rear = this.capacity - 1; this.array = new Object[this.capacity]; } public boolean isFull() { return this.queueCount == this.capacity; } public boolean isEmpty() { return this.queueCount == 0; } public void enQueue(Object value) { if (isFull()) throw new RuntimeException("Queue is Full!"); //队尾向后移动一位,即将插入数据的位置 this.rear = (this.rear+1)%this.capacity; //插入数据,入队 this.array[this.rear] = value; //队列大小加1 this.queueCount++; } public Object deQueue() { if (isEmpty()) throw new RuntimeException("Queue is Empty!"); Object tmpObject = this.array[this.front]; //队首向后移动一位,以前的位置即出队的元素。 this.front = (this.front+1)%this.capacity; //队列大小减1 this.queueCount--; return tmpObject; } public void displayQueue() { if (isEmpty()) { System.out.println("front:null<-...<-rear:null"); System.out.println("This is an empty queue!"); System.out.println(); return; } int tmp = this.front; System.out.println("front:["+ this.front + "]" + this.array[this.front] +"<-...<-rear:[" + this.rear + "]" + this.array[this.rear]); while(tmp != this.rear) { System.out.print(this.array[tmp] + "<-"); tmp = (tmp + 1)%this.capacity; } if (tmp == this.rear) System.out.println(this.array[tmp]); System.out.println(); } }
实验结果:
front:null<-...<-rear:null This is an empty queue! front:[0]1<-...<-rear:[3]4 1<-2<-3<-4 front:[2]3<-...<-rear:[3]4 3<-4 front:[2]3<-...<-rear:[1]20 3<-4<-10<-20