文章出处:极客时间《数据结构和算法之美》-作者:王争。该系列文章是本人的学习笔记。
1 队列
队列:可以想象成排队买票,先来的人先买,后到的人站在队尾。先进者先出,这就是队列。
队列的操作:入队(enqueue)、出队(dequeue)。入队:在队列尾部添加一个元素。出队:在队列头部移除一个元素。
2 队列的实现
队列的实现可以用数组,也可以用链表,分别称为顺序队列、链式队列。
2.1 基于数组的实现
public class ArrayQueue {private String[] items;private int n = 0;private int head;private int tail;public ArrayQueue(int capacity){this.n = capacity;items = new String[this.n];head = tail = 0;}/*** 在队尾插入一个元素* @param value* @return*/public boolean enqueue(String value){if(tail >=n) return false;//超出容量items[tail] = value;tail++;return true;}/*** 出队:从对头删除一个元素* @return*/public String dequeue(){if(head==tail) return null;String ret = items[head];head++;return ret;}
}
举个 例子,画画图可以发现,随着入队、出队操作,head、tail指针不断向右移动,极端情况是head、tail都在右边,即使数组有空间,也不能再做入队操作。
如何解决这个问题呢?我们可以在不能入队的时候,将head、tail之间的元素,拷贝到[0,tail-head]之间。
public boolean enqueue(String value){if(tail==n){if(head==0) return false;for(int i=head;i<tail;i++){items[i-head] = items[i];}tail = tail-head;head = 0;}items[tail] = value;tail++;return true;}
在这个版本实现中,出队操作时间复杂度是O(1),入队时间复杂度是多少呢?直觉认为平均时间复杂度应该还是O(1)。无证明。
2.2 基于链表的实现

需要两个指针head、tail分别指向队列头部、队列尾部。入队的时候tail.next=new node,tail=tail.next;出队的时候,head=head.next。
代码
3 循环队列
3.1 数组实现
在2.1基于数组实现的队列中有数据迁移的操作。如果是一个循环队列就不需要数据迁移操作了。循环队列是指它长的像一个环,原本队列头部、和队列尾部形成一条直线。现在把队列头部和队列尾部扳成了一个环。
图中队列长度为8,head=4,tail=7。
当插入a的时候,items[7]=a,tail=(tail+1)%8=0。
当插入b的时候,items[0]=b,tail=(tail+1)%8=1。
通过这样的方式,就避免了数据搬移的工作。
循环队列的实现重点是判断什么时候为空,什么时候队列已满,不能再添加数据。
在一般队列中,当head=tail的时候,队列为空。循环队列也是。
在一般队列中,当tail=n的时候,队列已满,不能再添加数据。循环队列需要多举几个例子,画图总结规律。
图中head=4,tail=3,数组长度len=8。如果再插入数据tail=(tail+1)%8=head,tail=head,这与队列为空的条件重复了。所以,我们在(tail+1)%8=head的时候后,就不再插入数据。也就是说items[tail]是没有值的。
代码
3.2 基于链表实现
基于链表实现就更简单了,将tail.next=head即可。
4 并发队列和阻塞队列
阻塞队列就是在队列的基础上加了阻塞的操作。例如当队列为空的时候,在队列头部取数据的时候会进入阻塞状态,直到队列中有数据的时候才返回数据。当队列满了的时候,往队列中插入数据的时候会进入阻塞状态,直到队列中有空间存储数据的时候才入队成功,然后返回。
其实,这就是一个生产者-消费者模型。
这种生产者消费者模型可以有效协调生产速度和消费速度。例如当生产者生产速度过快,消费者来不及消费的情况下,生产者很快会填满队列,生产者进入阻塞状态,只有当消费者消费之后,有了剩余空间,生产者才能继续生产。
不仅如此,我们开可以分配1个生产者,多个消费者。
在多线程情况下就会存在线程安全问题。如何实现一个线程安全的队列呢?线程安全的队列被称为并发队列。用CAS,可以实现一个高效地安全队列。
5 队列应用在有限资源的场景下
队列主要应用在资源有限的场景下。例如数据库连接池里的连接是有限的,服务器每秒能处理的请求量是有限的。
当服务器的请求池中有空闲的服务线程的时候,我们希望公平地处理每一个到访的请求。这个时候我们将请求加入到队列中。当服务器的请求池中有空闲服务线程的时候,从队列取请求,进行服务。
队列的实现有两种方式:数组和链表。链表实现的队列可以无限扩容,可以接受无限的请求,但这容易引起内存溢出。数组实现的队列只能接受一定个数的请求,超出数量,直接返回无法访问。但是队列大小的设定需要权衡用户体验以及服务器的内存大小。