本篇是数据结构与算法的第三篇,本篇我们将来了解一下知识点:
队列的抽象数据类型
顺序队列的设计与实现
链式队列的设计与实现
队列应用的简单举例
优先队列的设置与实现双链表实现
队列的抽象数据类型
队列同样是一种特殊的线性表,其插入和删除的操作分别在表的两端进行,队列的特点就是先进先出(First In First Out)。我们把向队列中插入元素的过程称为入队(Enqueue),删除元素的过程称为出队(Dequeue)并把允许入队的一端称为队尾,允许出的的一端称为队头,没有任何元素的队列则称为空队。其一般结构如下:
关于队列的操作,我们这里主要实现入队,出队,判断空队列和清空队列等操作,声明队列接口Queue(队列抽象数据类型)如下:
1 /**
2 * 队列抽象数据类型3 */
4 public interface Queue{5
6 /**
7 * 返回队列长度8 *@return
9 */
10 intsize();11
12 /**
13 * 判断队列是否为空14 *@return
15 */
16 booleanisEmpty();17
18 /**
19 * data 入队,添加成功返回true,否则返回false,可扩容20 *@paramdata21 *@return
22 */
23 booleanadd(T data);24
25 /**
26 * offer 方法可插入一个元素,这与add 方法不同,27 * 该方法只能通过抛出未经检查的异常使添加元素失败。28 * 而不是出现异常的情况,例如在容量固定(有界)的队列中29 * NullPointerException:data==null时抛出30 *@paramdata31 *@return
32 */
33 booleanoffer(T data);34
35 /**
36 * 返回队头元素,不执行删除操作,若队列为空,返回null37 *@return
38 */
39 T peek();40
41 /**
42 * 返回队头元素,不执行删除操作,若队列为空,抛出异常:NoSuchElementException43 *@return
44 */
45 T element();46
47 /**
48 * 出队,执行删除操作,返回队头元素,若队列为空,返回null49 *@return
50 */
51 T poll();52
53 /**
54 * 出队,执行删除操作,若队列为空,抛出异常:NoSuchElementException55 *@return
56 */
57 T remove();58
59 /**
60 * 清空队列61 */
62 voidclearQueue();63 }
下面我们就来分别实现顺序队列和链式队列
顺序队列的设计与实现
关于顺序队列(底层都是利用数组作为容器)的实现,我们将采用顺序循环队列的结构来实现,在给出实现方案前先来分析一下为什么不直接使用顺序表作为底层容器来实现。实际上采用顺序表实现队列时,入队操作直接执行顺序表尾部插入操作,其时间复杂度为O(1),出队操作直接执行顺序表头部删除操作,其时间复杂度为O(n),主要用于移动元素,效率低,既然如此,我们就把出队的时间复杂度降为O(1)即可,为此在顺序表中添加一个头指向下标front和尾指向下标,出队和入队时只要改变front、rear的下标指向取值即可,此时无需移动元素,因此出队的时间复杂度也就变为O(1)。其过程如下图所示
以上是添加front和rear下标记录的顺序表插入过程
从图的演示过程,(a)操作时,是空队列此时front和rear都为-1,同时可以发现虽然我们通过给顺序表添加front和rear变量记录下标后使用得出队操作的时间复杂度降为O(1),但是却出现了另外一个严重的问题,那就是空间浪费,从图中的(d)和(e)操作可以发现,20和30出队后,遗留下来的空间并没有被重新利用,反而是空着,所以导致执行(f)操作时,出现队列已满的假现象,这种假现象我们称之为假溢出,之所以出现这样假溢出的现象是因为顺序表队列的存储单元没有重复利用机制,而解决该问题的最合适的方式就是将顺序队列设计为循环结构,接下来我们就通过循环顺序表来实现顺序队列。
顺序循环队列就是将顺序队列设计为在逻辑结构上收尾相接的循环结构,这样我们就可以重复利用存储单元,其过程如下所示:
简单分析一下:
其中采用循环结构的顺序表,可以循环利用存储单元,因此有如下计算关系(其中size为队列长度):
//其中front、rear的下标的取值范围是0~size-1,不会造成假溢出。
front=(front+1)%size;//队头下标
rear=(rear+1)%size;
front为队头元素的下标,rear则指向下一个入队元素的下标
当front=rear时,我们约定队列为空。
出队操作改变front下标指向,入队操作改变rear下标指向,size代表队列容量。
约定队列满的条件为front=(rear+1)%size,注意此时队列中仍有一个空的位置,此处留一个空位主要用于避免与队列空的条件front=rear相同。
队列内部的数组可扩容,并按照原来队列的次序复制元素数组
了解了队列的实现规则后,我们重点分析一下入队add方法和出队poll方法,其中入队add方法实现如下:
/*** data 入队,添加成功返回true,否则返回false,可扩容
*@paramdata
*@return
*/@Overridepublic booleanadd(T data) {//判断是否满队
if (this.front==(this.rear+1)%this.elementData.length){
ensureCapacity(elementData.length*2+1);
}//添加data
elementData[this.rear]=data;//更新rear指向下一个空元素的位置