队列

队列是一种先进先出的结构,假设你在排队,那么最先排队的人最先得到服务。我们只能从队尾添加元素,从队首取出元素。老规矩,我们首先规定一下队列Queue的API

  1. public interface Queue<E> {
  2. //向队列中添加一个元素
  3. void enqueue(E e);
  4. //从队列中取出一个元素
  5. E dequeue();
  6. //获得队首的元素
  7. E getFront();
  8. //获取队列中元素的个数
  9. int getSize();
  10. //判断队列是否为空
  11. boolean isEmpty();
  12. }

数组队列

现在我们将使用动态数组Array类来实现队列,实现的逻辑也十分的简单,如下

  1. public class ArrayQueue<E> implements Queue<E> {
  2. private Array<E> array;
  3. public ArrayQueue() {
  4. array = new Array<>();
  5. }
  6. public ArrayQueue(int capacity) {
  7. array = new Array<>(capacity);
  8. }
  9. @Override
  10. public void enqueue(E e) {
  11. array.addLast(e);
  12. }
  13. @Override
  14. public E dequeue() {
  15. return array.removeFirst();
  16. }
  17. @Override
  18. public E getFront() {
  19. return array.getFirst();
  20. }
  21. @Override
  22. public int getSize() {
  23. return array.getSize();
  24. }
  25. @Override
  26. public boolean isEmpty() {
  27. return array.isEmpty();
  28. }
  29. @Override
  30. public String toString() {
  31. StringBuilder res = new StringBuilder();
  32. res.append("Queue: ");
  33. res.append("front [");
  34. for (int i = 0; i < array.getSize(); i++) {
  35. res.append(array.get(i));
  36. if (i != array.getSize()-1) {
  37. res.append(", ");
  38. }
  39. }
  40. res.append("] tail");
  41. return res.toString();
  42. }
  43. }

注意上面我们的dequeue操作是调用了动态数组的removeFirst操作,这个操作需要遍历整个数组将元素向前移动,所以该操作是O(n)的。

循环队列

上面队列的dequeue操作是O(n)级别的,这是因为上面会将数组整体向前移一位,但是如果我们不这么做,而是增加一个变量front来记录队首的位置,这样我们只要将front向前移一位即可,这样的操作就是O(1)级别的
队列 - 图1
这样做的同时,我们发现,如果当tail来到数组的末尾,按道理应该将数组进行扩容,但是front前面还有空间
队列 - 图2
这个时候我们应当将tail移动到数组头去
队列 - 图3
这时tail的计算公式不再是简单的tail = tail + 1,而是tail = (tail + 1) % data.length,如果不理解这个式子,就想象一下时钟,11点向前一步就是12点,也可以称为是0点,这个时候时钟的计算公式为(11 + 1) % 12。因为这种循环的特性,我们把这种实现方式称为循环队列。这次我们实现队列不在使用上面的动态数组,有了上面实现栈和队列的经验,想必可以容易理解下面的代码(在关键的步骤给予注释)

  1. public class LoopQueue<E> implements Queue<E> {
  2. private int front;
  3. private int tail;
  4. //队列中元素的个数
  5. private int size;
  6. //底层实现的数组
  7. private E[] data;
  8. //构造方法初始化
  9. public LoopQueue(int capacity) {
  10. data = (E[]) new Object[capacity];
  11. size = 0;
  12. front = 0;
  13. tail = 0;
  14. }
  15. //默认容量为10
  16. public LoopQueue() {
  17. this(10);
  18. }
  19. @Override
  20. public void enqueue(E e) {
  21. //首先判断数组是不是满了,如果是那么就进行扩容
  22. if (size == data.length) {
  23. resize(2 * data.length);
  24. }
  25. //向队尾添加元素
  26. data[tail] = e;
  27. //tail向后移动 不是简单的+1 上面已有解释
  28. tail = (tail +1) % data.length;
  29. size++;
  30. }
  31. //数组伸缩操作,已接触过
  32. private void resize(int newCapacity) {
  33. E[] temp = (E[]) new Object[newCapacity];
  34. for (int i =0; i < size; i++) {
  35. //这里我们将队列的头对应到新数组的开头
  36. temp[i] = data[(front + i)%data.length];
  37. }
  38. //重新记录front和tail的位置
  39. front = 0;
  40. tail = size;
  41. data = temp;
  42. }
  43. @Override
  44. public E dequeue() {
  45. //如果队列为空,抛出异常
  46. if (size == 0) {
  47. throw new IllegalArgumentException("队列为空");
  48. }
  49. //获得出队的元素
  50. E e = data[front];
  51. data[front] = null;
  52. //front向前移动(带循环)
  53. front = (front + 1) % data.length;
  54. size--;
  55. //缩容操作,不做解释
  56. if (size == data.length / 4) {
  57. resize(data.length / 2);
  58. }
  59. return e;
  60. }
  61. @Override
  62. public E getFront() {
  63. if (size == 0) {
  64. throw new IllegalArgumentException("队列为空");
  65. }
  66. return data[front];
  67. }
  68. @Override
  69. public int getSize() {
  70. return size;
  71. }
  72. @Override
  73. public boolean isEmpty() {
  74. return size == 0;
  75. }
  76. @Override
  77. public String toString() {
  78. StringBuilder str = new StringBuilder();
  79. str.append("Queue: size " + size);
  80. str.append(" capacity " + data.length);
  81. str.append("\nfront [");
  82. for (int i = 0; i < size; i++) {
  83. if (i == size - 1) {
  84. str.append(data[(front + i) % data.length].toString());
  85. } else {
  86. str.append(data[(front + i) % data.length].toString() + ", ");
  87. }
  88. }
  89. str.append("] tail");
  90. return str.toString();
  91. }
  92. }

这次我们得到的dequeue操作就是O(1)的了(严格的讲均摊复杂度为O(1),因为里面resize()复杂度是O(n)的)。