栈(Stack)是一种后入先出(Last In First Out, LIFO)的线性数据结构, 只能从一端添加或取出元素,进出元素的一端称为栈顶。
队列(Queue)是一种先入先出(First In First Out, FIFO)的线性数据结构,只能从一端添加元素,从另一端取出元素,添加元素一端称为队尾,取出元素的一端称为队首。
1.栈(Stack)
1.1 栈的接口中定义的几个操作
方法 | 描述 |
---|---|
int getSize() | 获取栈中元素个数 |
boolean isEmpty() | 判断栈是否为空 |
void push(E e) | 入栈,向栈中压入元素 e |
E pop() | 出栈,弹出栈顶元素 |
E peek() | 查看栈顶元素 |

1 | public interface Stack<E> { |
1.2 基于 Array 类的栈实现
如果用之前实现的动态数组 Array 类来实现栈,很多方法可以直接复用。由于在数组的末尾添加元素和删除元素的复杂度为 O(1),因此将数组的末尾作为栈顶。
在实现栈中 push(E e)
和 pop()
方法时,根据后入先出的原则,分别复用 Array 中的 addLast(E e)
和 removeLast()
方法。peek()
操作只是查看栈顶元素,并非删除,因此复用 Array 类中的 getLast()
方法。
另外再添加 getCapacity()
方法,返回当前栈的容量,具体实现是返回底层数组的容量。
1 | public class ArrayStack<E> implements Stack<E> { |
为了方便打印输出,重写一下 toString()
方法。数组栈的整体实现:ArrayStack.java
1 |
|
1.3 栈的复杂度分析
由于 push(E e)
和 pop()
操作都是在数组尾操作,因此时间复杂度是 O(1)
,如果触发 resize()
操作,按照均摊复杂度分析,整个过程也是 O(1)
。
方法 | 复杂度 |
---|---|
int getSize() | O(1) |
boolean isEmpty() | O(1) |
void push(E e) | O(1) 均摊 |
E pop() | O(1) 均摊 |
E peek() | O(1) |
2. 队列(Queue)
2.1 队列的接口中定义的几个操作
方法 | 描述 |
---|---|
void enqueue(E e) | 入队,向队列中添加新元素 e |
E dequeue() | 出队,从队列中删除元素 |
E getFront() | 查看队首元素 |
int getSize() | 获取队列中元素个数 |
boolean isEmpty() | 判断队列是否为空 |
1 | public interface Queue<E> { |

2.2 基于 Array 类的队列实现
还是用之前实现的动态数组 Array 类来实现队列,很多方法可以直接复用。
在实现队列中 enqueue(E e)
和 dequeue()
方法时,根据先入先出的原则,分别复用 Array 中的 addLast(E e)
和 removeFirst()
方法。getFront()
操作只是查看队首元素,并非删除,因此复用 Array 类中的 getFirst()
方法。
另外再添加 getCapacity()
方法,返回当前栈的容量,具体实现是返回底层数组的容量。
1 | public class ArrayQueue<E> implements Queue<E> { |
为了方便打印输出,重写 toString()
方法。数组队列的整体实现:ArrayQueue.java。
1 |
|
2.3 队列的复杂度分析
由于 enqueue(E e)
操作是在数组尾操作,因此时间复杂度是 O(1)
,如果触发 resize()
操作,按照均摊复杂度分析,入队过程也是 O(1)
。
但出队操作是在数组头部进行,底层复用动态数组类中 removeFirst()
方法,因此时间复杂度为 O(n)。
方法 | 复杂度 |
---|---|
void enqueue(E e) | O(1) 均摊 |
E dequeue() | O(n) |
E getFront() | O(1) |
int getSize() | O(1) |
boolean isEmpty() | O(1) |
3. 循环队列
3.1 循环队列介绍
之前用动态数组实现的队列,出队操作由于复用了动态数组中 removeFirst()
操作,删除第一个元素后,后面的元素需要向前依次移动,因此时间复杂度为 O(n)。

考虑出队后,其余元素不进行移动,这样就可以使出队的时间复杂度变为 O(1)
。
当删除数组第一个元素后,不进行后面元素的移动,而是用一个 front
变量记录下当前第一个元素的索引位置,即队首元素位置,同时用一个 tail
变量记录下最后一个元素的下一个索引位置,也是新元素入队时的索引位置。这样当删除数组第一个元素后,只需要维护 front
就可以。

循环队列的初始状态下,队列中没有元素,front
和 tail
均指向底层数组的第一个位置,因此当 front == tail
时,队列为空。
进行入队操作时,只需维护 tail
;如果进行出队操作,只需维护 front
。
当底层数组中最后一个位置存在元素,再进行入队操作时,tail
就无法进行简单的 tail++
操作,但是由于进行了出队操作,底层数组的前半部分是空闲的,如下图中所示,这时 tail
应该指向 data[0]
,因此要实现这种循环的操作,tail
的维护应该变成 tail = (tail + 1) % data.length
。
当底层数组中 tail + 1 == front
时,再添加元素,此时 front == tail
,而前面定义这种情况是队列为空的判断条件,因此在这种情况下,选择不再添加元素,即判断队列为满的条件是 tail + 1 == front
,但由于是循环队列,这个判断条件响应改为 (tail + 1) % data.length == front
,如同钟表一样,11 点的下一个小时我们可以说是 12 点或者是 0
点,钟表中的 12 个刻度对应底层数组的长度 data.length
。这时浪费了一个数组空间,因此当前队列所能存储最多的元素个数为 data.length - 1
。

3.2 循环队列中简单方法的实现
先进行 LoopQueue
类中简单操作的实现,由于实现方式不同,这里不再复用之前的 Array
动态数组类。
1 | public class LoopQueue<E> implements Queue<E> { |
3.3 底层数组容量调整
底层数组的容量调整和之前 Array
中 resize(int newCapacity)
方法类似。差别在于两个地方:遍历旧数组、维护 front
和 tail
。
为了保证元素的顺序,必须从 front
开始遍历旧的 data
数组时,但旧数组中 front
可能不为 0
,即不能进行 newData[i] = data[i]
这种操作,这时 data
中元素相对于 newData
中元素的索引值存在 front 数量的偏移,同时由于是循环队列,有可能会产生越界,需要再对 data.length
进行取模。这里也有两种遍历方式。
方式一:终止条件从 size
考虑。
1 | for (int i = 0; i < size; i++) { |
方式二:终止条件从 tail
考虑。
1 | for (int i = front ; i != tail ; i = (i + 1) % data.length) { |
在容量调整后,front
重新指向了新数组索引为 0
的位置,tail
重新指向了新数组中索引为 size
的位置。
1 | private void resize(int newCapacity) { |
3.4 循环队列入队、出队的实现
循环队列底层数组的添加、删除和容量调整的方式和 Array
中的方法基本是相同的,差别主要在于维护 front
和 tail
变量时并不是简单加一,而是需要加一并取模数组长度。
1 |
|
1 |
|
重写 toString()
方法,循环队列的整体实现:LoopQueue.java
1 |
|
3.5 循环队列的时间复杂度
方法 | 复杂度 |
---|---|
void enqueue(E e) | O(1) 均摊 |
E dequeue() | O(1) 均摊 |
E getFront() | O(1) |
int getSize() | O(1) |
boolean isEmpty() | O(1) |