与栈的后进先出(LIFO,Last In Frist Out)不同,队列是先进先出(FIFO,Frist In First Out),在现实中就像排队买票一样,每个人都得从队尾排队,然后排在队前的人才能先拿到票。
队列也是基本的数据结构,可以用双端链表(或者双向链表)、数组存储,我们这里用数组存储来解释循环队列。
什么是循环队列捏?假如有一辆过山车有5个位子,规定,进入过山车的人只能从车后进,车前出。这样,当五个人进入过山车后,这个队列就满了。这时,坐在最前面的人走了,应该有了个空座,可以进去一个人,但是按照规定人只能从车后进,所以不能进人,而循环队列就是相当把最后一个位子移到最后,从而使人进去。换成数组的话,就是把数组头与尾相连,形成一个圆(循环)。而使用这个数组存储的队列就叫循环队列。
循环队列与一般队列不同,需要做边界判断,因为你不知道,这个数组是否已经循环过,尤其是在做判空和判满操作时,你得考虑两种情况,在《Java数据结构与算法》中,作者做过处理,但是还是不好理解,尤其是当你没插一个数据与插满数据,队头标识位又回到起点的情况,所以我在程序中添加了一个是否已经循环的标识位,相当于,对队头队尾是否已经颠倒。其代码如下:
package test.queue; public class Queue { //存储数组、队头、队尾、是否已经循环 private int[] items; private int head; private int tail; private boolean isTraned; public Queue(int size) { items = new int[size]; head = 0; tail = 0; isTraned = false; } //反转,用于控制计算整个队列的长度时,是否要跨边界。 private void tran() { isTraned = !isTraned; } //计算长度 public int size() { if(isTraned)//已经循环,需要跨边界计算 return items.length + tail - head; else return tail - head; } //判空 public boolean isEmpty() { return size()<=0; } //判满 public boolean isFull() { return size()>=items.length; } //插入 public void insert(int in) { if(isFull()) throw new IndexOutOfBoundsException("队列已满"); else { items[tail++] = in; if(tail == items.length) { tail = 0; tran(); } } } //移除 public int remove(){ int out = 0; if(isEmpty()) throw new IndexOutOfBoundsException("队列为空"); else { out = items[head++]; if(head == items.length) { head = 0; tran(); } } return out; } //查看 public int peek(){ if(isEmpty()) throw new IndexOutOfBoundsException("队列为空"); else return items[head]; } public static void main(String[] args) { Queue q = new Queue(3); q.insert(3); q.insert(2); System.out.println(q.remove()); System.out.println(q.remove()); q.insert(11); q.insert(12); System.out.println(q.remove()); System.out.println(q.remove()); q.insert(100); q.insert(101); q.insert(102); System.out.println(q.remove()); System.out.println(q.remove()); System.out.println(q.remove()); } }
运行一下:
3 2 11 12 100 101 102
大家可以试试,如果在q.insert(102);后再insert。或者System.out.println(q.remove());再remove都会出现IndexOutOfBoundsException错误。
我们再来看看优先级队列。
优先级队列可以这样理解,就好像一个奇怪的电影院,也需要排队买票,但是他对规定:必须按照从高到矮的顺序排队。也就是说,新来的人必须在队伍中找到高矮正合适的位置,而先拿到票的永远是队伍中最矮的。
相对于前面循环队列,优先级队列没有队尾,队头即元素数,也不用判断边界,所不同的是需要一个插入算法,其代码如下:
package test.queue; public class PriorityQueue { //存储数组、已有元素个数 private int[] items; private int itemNum; public PriorityQueue(int size) { items = new int[size]; itemNum = 0; } //个数 public int size() { return itemNum; } //判满 public boolean isFull() { return itemNum >= items.length; } //判空 public boolean isEmpty() { return itemNum <= 0; } //移除 public int remove() { if(isEmpty()) throw new IndexOutOfBoundsException("队列为空"); else return items[--itemNum]; } //插入 public void insert(int in) { if(isFull()) throw new IndexOutOfBoundsException("队列已满"); else { int i = itemNum - 1; while(i >= 0 && items[i] < in)//按照从小到大的顺序排列,这里有点像插入排序。 { items[i+1]=items[i]; i--; } items[i+1] = in; itemNum++; } } //查看队前元素 public int peek() { return items[itemNum - 1]; } public static void main(String[] args) { PriorityQueue pq = new PriorityQueue(3); pq.insert(8); pq.insert(4); pq.insert(6); System.out.println(pq.remove()); System.out.println(pq.remove()); System.out.println(pq.remove()); } }
运行一下:
4 6 8
大家可以看出的确是从小到大输出了。