循环队列
一、普通队列的弊端
队列:是一种可以分别在两端进行增删的特殊线性表。既然是线性表,那么可以使用顺序存储和链式存储来实现,如果是链式存储的话,那么增删的复杂度都是O(1),这一点很好理解,应该只要修改一下指针就好了,如果是顺序存储的话,在队尾增加的时间复杂度是O(1),但是在队头进行删除的时候,会涉及到迁移操作,这时候的时候复杂度就是O(n),所以一般来讲不会直接使用顺序存储的方式来实现普通队列。
二、循环队列的原理概述
较之普通队列用顺序存储来实现存在删除带来的时间损耗,怎么去避免它,因为普通队列删除队头结点,会将后续结点进行整体的一个前移,所以才会带来O(n)的时间复杂度,如果不前移结点,而是调整头结点的位置,删除一个结点,就将头结点往后移动一位。
TIP:front是指向头结点的指针,rear是指向下一个要插入结点的指针
队列初始状态
删除节点后状态
那么这样子是可以避免删除带来的时间损耗了,但是可以发现当往下标4插入数据后,rear指针该何去何从,放在脚标5?那么会报数组越界,而且很明显下标0和1的位置都空着,所以rear应该指向0才对,那么这种实现方式其实就是循环列表。
循环队列
实现循环列表存在几个要考虑的事情:-
1:啥时候表示队列空着?
一般在循环队列的初始状态,可以将front和rear指针都指向下标0,当插入元素时,rear会顺时针方向累加,当删除元素时,front也会顺时针方向累加,那么删的和加的一样多,其实在同一个方向走的下标也一样多,那么自然容易想到当_front == rear_时,表示队列为空。只不过这里需要注意一点,因为是循环队列,front和rear的值不能无限递增,比如一个数组,大小为5,最大下标是4,难道还能4+1=5,出来个5下标,明显不可能,所以,这里的递增,需要这么操作:(front + 1) % maxSize,巧妙的用到了取模运算符。
2:啥时候表示队列满了?
队列啥时候算满呢?其实可以发现front可能在rear的后面,也有可能在rear的前面,仔细想想可能会发现某种情况下,当rear == front也是队列满的时候,但是这样就跟队列空着的时候,判断条件一致了,这样明显也不行,为了区分开来,单独留一个结点不存值,当rear和front相差一个结点时,即证明循环队列已满。判断条件为:**(rear + 1) % maxSize = front**
循环队列满的状态
3:怎么才能知道队列当前含有元素的多少?
由于循环队列的特性,队列里面的元素可能是连续的,也有可能是分成两段的,那么怎么去统计含有多少元素呢?可以使用这个公式(rear - front + maxSize) % maxSize,因为rear - front可能为正数,也有可能会负数,为正数表示是连续的,为负数表示不连续的。
三、循环队列的实现-java
1 | public class CircularQueue<E> { |
四、循环队列时间复杂度分析
可以很明显发现,循环队列的增删操作时间复杂度都是O(1),并且还能实现充分利用申请到的空间,所以如果能够确认一个队列的大小,那么就使用顺序方式来实现,并且是循环队列,而不是普通队列。如果确认不了,那么还是使用链式方式来实现。