一、引言
栈是一种后进先出(First in First out FIFO)的线性数据结构,队列从队尾添加元素,从队首取出元素。
二、实现
- 队列接口
/**
* 队列接口
* @param <E>
*/
public interface Queue<E> {
/**
* 获取队列中元素个数
* @return 队列中元素个数
*/
int getSize();
/**
* 判断是否空队列
* @return
*/
boolean isEmpty();
/**
* 入队操作
* @param e 入队元素
*/
void enqueue(E e);
/**
* 出队操作
* @return 出队元素
*/
E dequeue();
/**
* 查询队首元素
* @return 队首元素
*/
E getFront();
}
1、基于动态数组实现队列
- 动态数组类
/**
* 动态数组,数组二次封装
*/
public class Array<E> {
/**
* 基于Java原生数组,保存数据的容器
*/
private E[] data;
/**
* 当前元素个数
*/
private int size;
public Array(int capacity) {
data = (E[]) new Object[capacity];
size = 0;
}
/**
* 默认数组容量capacity=10
*/
public Array() {
this(10);
}
/**
* 获取数组中元素个数
* @return
*/
public int getSize() {
return size;
}
/**
* 获取数组的容量
* @return
*/
public int getCapacity() {
return data.length;
}
/**
* 判断数组是否为空
* @return
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 在所有元素后面添加新元素
* @param e 元素
*/
public void addLast(E e) {
add(size, e);
}
/**
* 在所有元素前面添加新元素
* @param e 元素
*/
public void addFirst(E e) {
add(0, e);
}
/**
* 向index索引位置插入一个新元素e
* @param index 数组索引位置
* @param e 元素
*/
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("addList failed. index < 0 || index > size");
}
//空间不足,扩容
if (size == data.length) {
resize(2 * data.length);
}
for (int i = size - 1; i >= index; i--) {
data[i + 1] = data[i];
}
data[index] = e;
size++;
}
/**
* 根据元素索引获取数组元素
* @param index 索引
* @return
*/
public E get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("get failed. index is illegal");
}
return data[index];
}
/**
* 获取数组最后一个元素
* @return
*/
public E getLast() {
return get(size - 1);
}
/**
* 获取数组第一个元素
* @return
*/
public E getFirst() {
return get(0);
}
/**
* 根据元素索引修改数组元素
* @param index 索引
* @param e 元素
* @return
*/
public void set(int index, E e) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("get failed. index is illegal");
}
data[index] = e;
}
/**
* 判断包含元素
* @param e 元素
* @return
*/
public boolean contains(E e) {
for (int i = 0; i < size; i++) {
if (data[i].equals(e)) {
return true;
}
}
return false;
}
/**
* 查找元素索引
* @param e 元素
* @return 返回元素索引,如果不存在则返回-1
*/
public int find(E e) {
for (int i = 0; i < size; i++) {
if (data[i].equals(e)) {
return i;
}
}
return -1;
}
/**
* 移除指定索引的元素
* @param index 索引
* @return 返回被移除的元素
*/
public E remove(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("get failed. index is illegal");
}
E ret = data[index];
for (int i = index + 1; i < size; i++) {
data[i - 1] = data[i];
}
size--;
data[size] = null;
//空间利用率低,数组缩容,防止复杂度震荡
if (size == data.length / 4 && data.length / 2 != 0) {
resize(data.length / 2);
}
return ret;
}
/**
* 移除第一个元素
* @return 返回被移除元素
*/
public E removeFirst() {
return remove(0);
}
/**
* 移除最后一个元素
* @return 返回被移除元素
*/
public E removeLast() {
return remove(size - 1);
}
/**
* 移除数组中一个元素
* @param e 元素
*/
public void removeElement(E e) {
int index = find(e);
if (index != -1) {
remove(index);
}
}
/**
* 数组容器扩容、缩容
* @param newCapacity 新的容量
*/
private void resize(int newCapacity) {
E[] newData = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newData[i] = data[i];
}
data = newData;
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append(String.format("Array: size = %d, capacity = %d\n", size, data.length));
res.append("[");
for (int i = 0; i < size; i++) {
res.append(data[i]);
if (i != size - 1) {
res.append(", ");
}
}
res.append("]");
return res.toString();
}
}
- 队列实现
/**
* 队列的数组实现方式
* @param <E>
*/
public class ArrayQueue<E> implements Queue<E> {
/**
* 动态数组
*/
private Array<E> array;
/**
* 初始化队列容量
* @param capacity
*/
public ArrayQueue(int capacity) {
array = new Array<>(capacity);
}
/**
* 默认初始化队列
*/
public ArrayQueue() {
array = new Array<>();
}
@Override
public int getSize() {
return array.getSize();
}
@Override
public boolean isEmpty() {
return array.isEmpty();
}
/**
* 获取队列容量
* @return 队列容量
*/
public int getCapacity() {
return array.getCapacity();
}
@Override
public void enqueue(E e) {
array.addLast(e);
}
@Override
public E dequeue() {
return array.removeFirst();
}
@Override
public E getFront() {
return array.getFirst();
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append("Queue: ");
res.append("front [");
for (int i = 0; i < array.getSize(); i++) {
res.append(array.get(i));
if (i != array.getSize() - 1) {
res.append(", ");
}
}
res.append("] tail");
return res.toString();
}
}
2、循环队列
- 队列实现
/**
* 循环队列
* @param <E>
*/
public class LoopQueue<E> implements Queue<E> {
/**
* 数组容器
*/
private E[] data;
/**
* 队首游标
*/
private int front;
/**
* 队尾游标
*/
private int tail;
/**
* 队列中元素个数
*/
private int size;
/**
* 初始化队列
* @param capacity
*/
public LoopQueue(int capacity) {
//队列浪费一个空间,为了好判断队列是否为空
data = (E[]) new Object[capacity + 1];
front = 0;
tail = 0;
size = 0;
}
/**
* 默认初始化队列
*/
public LoopQueue() {
this(10);
}
public int getCapacity() {
return data.length - 1;
}
/**
* 获取队列中元素个数
* @return
*/
@Override
public int getSize() {
return size;
}
/**
* 判断队列是否为空,队列满元素时,队首尾游标不重合,默认浪费一个数组空间
* @return
*/
@Override
public boolean isEmpty() {
return front == tail;
}
/**
* 入队操作
* @param e 入队元素
*/
@Override
public void enqueue(E e) {
//队列满元素(浪费一个空间)
if ((tail + 1) % data.length == front) {
resize(getCapacity() * 2);
}
data[tail] = e;
tail = (tail + 1) % data.length;
size ++;
}
@Override
public E dequeue() {
if (isEmpty()) {
throw new IllegalArgumentException("cannot dequeue from an empty queue");
}
E ret = data[front];
data[front] = null;
front = (front + 1) % data.length;
size--;
//缩容
if (size == getCapacity() / 4 && getCapacity() / 2 != 0) {
resize(getCapacity() / 2);
}
return ret;
}
@Override
public E getFront() {
if (isEmpty()) {
throw new IllegalArgumentException("cannot dequeue from an empty queue");
}
return data[front];
}
/**
* 队列扩缩容
* @param newCapacity 容量
*/
private void resize(int newCapacity) {
E[] newData = (E[]) new Object[newCapacity + 1];
for (int i = 0; i < size; i++) {
newData[i] = data[(i + front) % data.length];
}
data = newData;
front = 0;
tail = size;
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append(String.format("Queue: size = %d , capacity = %d\n", size, getCapacity()));
res.append("front [");
for (int i = front; i != tail; i = (i + 1) % data.length) {
res.append(data[i]);
if ((i + 1) % data.length != tail) {
res.append(", ");
}
}
res.append("] tail");
return res.toString();
}
public static void main(String[] args) {
LoopQueue<Integer> queue = new LoopQueue<>();
for (int i = 0; i < 10; i++) {
queue.enqueue(i);
System.out.println(queue);
if (i % 3 == 2) {
queue.dequeue();
System.out.println(queue);
}
}
}
}
3、基于链表实现队列
- 链表队列实现
/**
* 基于链表实现队列
* @param <E>
*/
public class LinkedListQueue<E> implements Queue<E> {
/**
* 节点
*/
private class Node {
public E e;
public Node next;
public Node(E e,Node next) {
this.e = e;
this.next = next;
}
public Node(E e) {
this(e, null);
}
public Node() {
this(null, null);
}
@Override
public String toString() {
return e.toString();
}
}
/**
* 链表头节点
*/
private Node head;
/**
* 链表尾节点
*/
private Node tail;
/**
* 链表中元素个数
*/
private int size;
public LinkedListQueue() {
head = null;
tail = null;
size = 0;
}
/**
* 获取队列中元素个数
* @return
*/
@Override
public int getSize() {
return size;
}
/**
* 判断队列中是否有元素
* @return
*/
@Override
public boolean isEmpty() {
return size == 0;
}
/**
* 入队操作
* @param e 入队元素
*/
@Override
public void enqueue(E e) {
if (tail == null) {
tail = new Node(e);
head = tail;
} else {
tail.next = new Node(e);
tail = tail.next;
}
size++;
}
/**
* 出队操作
*/
@Override
public E dequeue() {
if (isEmpty()) {
throw new IllegalArgumentException("cannot dequeue from an empty queue");
}
Node removeNode = head;
head = head.next;
removeNode.next = null;
if (head == null) {
tail = null;
}
size--;
return removeNode.e;
}
/**
* 查询队首元素
* @return
*/
@Override
public E getFront() {
if (isEmpty()) {
throw new IllegalArgumentException("query is empty");
}
return head.e;
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append("queue: front ");
Node cur = head;
while (cur != null) {
res.append(cur + "->");
cur = cur.next;
}
res.append("NULL tail");
return res.toString();
}
public static void main(String[] args) {
LinkedListQueue<Integer> queue = new LinkedListQueue<>();
for (int i = 0; i < 10; i++) {
queue.enqueue(i);
System.out.println(queue);
if (i % 3 == 2) {
queue.dequeue();
System.out.println(queue);
}
}
}
}
3、基于最大堆实现优先队列
- 最大堆实现(依赖动态数组)
/**
* 基于动态数组实现最大堆
* @param <E>
*/
public class MaxHeap<E extends Comparable<E>> {
private Array<E> data;
public MaxHeap(int capacity) {
data = new Array<>(capacity);
}
public MaxHeap() {
data = new Array<>();
}
/**
* 普通数组堆化
* @param arr
*/
public MaxHeap(E[] arr) {
data = new Array<>(arr);
//从第一个非叶子节点(叶子节点无需下沉操作)开始遍历,并且执行下沉操作,完成堆化
for (int i = parent(arr.length - 1); i >= 0; i--) {
siftDown(i);
}
}
/**
* 获取堆中元素个数
* @return
*/
public int size() {
return data.getSize();
}
/**
* 判断堆中是否为空
* @return
*/
public boolean isEmpty() {
return data.isEmpty();
}
/**
* 返回二叉堆的数组表示中,一个索引所表示的元素的父亲节点的索引
* @param index 节点在数组中的索引
* @return
*/
private int parent(int index) {
if (index == 0) {
throw new IllegalArgumentException("index-0 doesn't have parent");
}
return (index - 1) / 2;
}
/**
* 返回二叉堆的数组表示中,一个索引所表示的元素的左孩子节点的索引
* @param index 节点在数组中的索引
* @return
*/
private int leftChild(int index) {
return index * 2 + 1;
}
/**
* 返回二叉堆的数组表示中,一个索引所表示的元素的右孩子节点的索引
* @param index 节点在数组中的索引
* @return
*/
private int rightChild(int index) {
return index * 2 + 2;
}
/**
* 向堆中添加元素
* @param e 待添加元素
*/
public void add(E e) {
data.addLast(e);
siftUp(data.getSize() - 1);
}
/**
* 堆中元素上浮
* @param k 元素索引
*/
private void siftUp(int k) {
//当前节点的元素比父亲节点的元素大则上浮
while (k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0) {
//交换数组中的元素
data.swap(k, parent(k));
k = parent(k);
}
}
/**
* 查询堆中最大元素
* @return
*/
public E findMax() {
if (data.getSize() == 0) {
throw new IllegalArgumentException("can not finMax when heap is empty");
}
return data.get(0);
}
/**
* 取出堆中最大元素
* @return
*/
public E extractMax() {
E max = findMax();
//交换堆中最大的元素与堆尾元素
data.swap(0, data.getSize() - 1);
//删除堆尾元素
data.removeLast();
//元素下沉
siftDown(0);
return max;
}
/**
* 堆中元素下沉
* @param k 元素索引
*/
private void siftDown(int k) {
//只要该元素的左孩子索引没有越界,继续处理
while (leftChild(k) < data.getSize()) {
int j = leftChild(k);
if (j + 1 < data.getSize() && data.get(j + 1).compareTo(data.get(j)) > 0) {
j = rightChild(k);
}
//data[j] 是leftChild 和 rightChild中的最大值
if (data.get(k).compareTo(data.get(j)) >= 0) {
break;
}
//下沉
data.swap(k, j);
k = j;
}
}
/**
* 取出堆中的最大元素,并且替换成元素e
* @param e 待替换元素
* @return 堆中最大元素
*/
public E replace(E e) {
E max = findMax();
//覆盖最大元素
data.set(0, e);
//被元素可能破坏了堆的结构,触发下沉操作
siftDown(0);
return max;
}
}
- 优先队列实现
/**
* 优先队列基于最大堆实现
* @param <E>
*/
public class PriorityQueue<E extends Comparable<E>> implements Queue<E> {
/**
* 最大堆容器
*/
private MaxHeap<E> maxHeap;
public PriorityQueue() {
maxHeap = new MaxHeap<>();
}
/**
* 获取队列中元素个数
* @return
*/
@Override
public int getSize() {
return maxHeap.size();
}
/**
* 判断队列是否为空
* @return
*/
@Override
public boolean isEmpty() {
return maxHeap.isEmpty();
}
/**
* 入队操作
* @param e 入队元素
*/
@Override
public void enqueue(E e) {
maxHeap.add(e);
}
/**
* 出队操作
* @return
*/
@Override
public E dequeue() {
return maxHeap.extractMax();
}
/**
* 查询队首元素
* @return
*/
@Override
public E getFront() {
return maxHeap.findMax();
}
}
三、性能测试
- 测试队列入队、出队耗时
import java.util.Random;
public class Main {
public static void main(String[] args) {
int opCount = 100000;
ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
double arrayQueueTime = testQueue(arrayQueue, opCount);
System.out.println("ArrayQueue, time:" + arrayQueueTime + "s");//ArrayQueue, time:3.0009159s
LoopQueue<Integer> loopQueue = new LoopQueue<>();
double loopQueueTime = testQueue(loopQueue, opCount);
System.out.println("LoopQueue, time:" + loopQueueTime + "s");//LoopQueue, time:0.0104942s
LinkedListQueue<Integer> linkedListQueue = new LinkedListQueue<>();
double linkedListQueueTime = testQueue(linkedListQueue, opCount);
System.out.println("LinkedListQueue, time:" + linkedListQueueTime + "s");//LinkedListQueue, time:0.008738501s
}
/**
* 测试使用队列运行入队、出队操作所需的时间
* @param queue 待测试队列
* @param opCount 操作次数
* @return
*/
private static double testQueue(Queue<Integer> queue, int opCount) {
long statTime = System.nanoTime();
Random random = new Random();
for (int i = 0; i < opCount; i++) {
queue.enqueue(random.nextInt(Integer.MAX_VALUE));
}
for (int i = 0; i < opCount; i++) {
queue.dequeue();
}
long endTime = System.nanoTime();
return (endTime - statTime) / 1000000000.0;
}
}
说明: 以上测试是在性能一般的个人笔记本上完成的,基于JDK1.8。分别进行了10万次入队出队操作,性能相差近300倍。这点得益于循环队列的实现是通过队首、队尾游标标识首尾元素的,循环队列在出队操作时不需要移动任何元素,只需要维护好游标。循环队列相比于基于动态数组队列的出队操作,时间复杂度从O(n)变成了O(1)(均摊);基于链表实现的队列由于有头节点和尾节点的存在,时间复杂度也是O(1)的。
四、复杂度分析
1、基于数组实现的复杂度分析
- enqueue(e) => O(1) : 入队操作是先动态数组末尾添加元素,时间复杂度与数组中元素个数无关,入队操作可能触发数组扩容,但是触发概率较小,均摊复杂度接近O(1)。
- dequeue() => O(n) : 出队操作移出的是数组第一个元素,其后所有元素都需要向前移动,时间复杂度跟元素个数正相关。
- getFront() => O(1) : 查询队首元素实际上是查询的是数组第一个元素,可通过数组索引迅速定位,不会造成元素移动。
- getSize() => O(1) : 实际上是数组的游标值,无需重新计算。
- isEmpty() => O(1) : 类似于getSize.
2、循环队列复杂度分析
- dequeue() => O(1) : 出队操作移出的是数组第一个元素,无需移动其后的元素,只需维护队首游标,虽然可能触发缩容操作(O(n)),此处的O(1)为均摊复杂度。
3、优先队列复杂度
- enqueue(e) => O(logn) : 入队操作是二叉堆元素下沉的一个过程,最多下沉操作次数是二叉堆这个完全二叉树的最大高度。
- dequeue() => O(logn) : 出队操作是二叉堆元素上浮的一个过程,最多上浮操作次数是二叉堆这个完全二叉树的最大高度。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。