当前位置:   article > 正文

【数据结构】队列实现的5种方式及时间复杂度对比分析

不等长队列对比算法

1. 使用数组实现一个简单的队列

  1. /**
  2. * ===========================
  3. * 队列首部 00000000000000000000000000 队列尾部
  4. * ===========================
  5. */
  6. public class ArrayQueue<Element> implements Queue<Element>{
  7. // 通过内部的array来实现
  8. private Array<Element> array;
  9. // 构造函数
  10. public ArrayQueue(int capacity){
  11. this.array = new Array<>(capacity);
  12. }
  13. // 默认的构造函数
  14. public ArrayQueue(){
  15. this.array = new Array<>();
  16. }
  17. @Override
  18. public int getSize() {
  19. return this.array.getSize();
  20. }
  21. @Override
  22. public boolean isEmpty() {
  23. return this.array.isEmpty();
  24. }
  25. public int getCapacity(){
  26. return this.array.getCapacity();
  27. }
  28. @Override
  29. public void enqueue(Element element) {
  30. // 进入队列(数组的末尾来添加元素)
  31. this.array.addLast(element);
  32. }
  33. @Override
  34. public Element dequeue() {
  35. // 出队列(删除最后一个元素),数组的第一个元素
  36. return this.array.removeFirst();
  37. }
  38. @Override
  39. public Element getFront() {
  40. // 获取第一个元素(对首部的第一个元素)
  41. return this.array.getFirst();
  42. }
  43. @Override
  44. public String toString(){
  45. StringBuilder stringBuilder = new StringBuilder();
  46. // 使用自定义的方式实现数组的输出
  47. stringBuilder.append("Queue:");
  48. // 开始实现数组元素的查询
  49. stringBuilder.append("front [");
  50. for (int i = 0; i < this.array.getSize(); i++) {
  51. stringBuilder.append(this.array.get(i));
  52. // 开始实现数组元素的回显(只要下表不是最后一个元素的话,就直接输出这个元素)
  53. if (i != this.array.getSize()-1)
  54. stringBuilder.append(", ");
  55. }
  56. stringBuilder.append("] tail");
  57. // 实现数组元素的输出
  58. return stringBuilder.toString();
  59. }
  60. }

2. 使用数组实现一个循环队列(维护一个size变量)

  1. /**
  2. * 循环队列的几个要点:
  3. * 1. tail = head 说明队列就是满的
  4. * 2. 循环队列需要空出来一个位置
  5. */
  6. public class LoopQueue<E> implements Queue {
  7. private E[] data;
  8. private int head; // 首部指针
  9. private int tail; // 尾部指针
  10. private int size; // 可以通过head以及tail的位置情况去计算size的大小【注意是不能直接使用getCapacity的】
  11. // 实现循环队列
  12. public LoopQueue() {
  13. // 设置一个队列的默认的大小
  14. this(10);
  15. }
  16. // 循环队列的实现
  17. public LoopQueue(int capacity) {
  18. this.data = (E[]) new Object[capacity + 1];
  19. // 需要多出来一个
  20. this.head = 0;
  21. this.tail = 0;
  22. this.size = 0;
  23. }
  24. @Override
  25. public int getSize() {
  26. // 计算容量大小
  27. return this.size;
  28. }
  29. // capacity表示的这个队列中最大可以容纳的元素个数【这是一个固定值】
  30. @Override
  31. public int getCapacity() {
  32. // 由于队列默认占了一个空的位置,因此sizt = this.length-1
  33. return this.data.length - 1;
  34. }
  35. // 当head和tail的值相同的时候队列满了
  36. public boolean isEmpty() {
  37. return head == tail;
  38. }
  39. @Override
  40. public void enqueue(Object value) {
  41. // 1. 开始判断队列有没有充满, 因为数组的下表是不会改变的,所以这里需要以数组的下标为一个参考点, 而不是this.data.length-14
  42. if ((tail + 1) % this.data.length == head) {
  43. // 2. 开始进行扩容, 原来的二倍, 由于默认的时候已经白白浪费了一个空间,因此这里就直接扩容为原来的二倍即可
  44. resize(2 * getCapacity());
  45. }
  46. // 2. 如果没有满的话,就开始添加元素
  47. this.data[tail] = (E) value;
  48. // 3. 添加完毕之后(tail是一个循环的位置,不可以直接相加)
  49. // this.tail = 2;
  50. this.tail = (this.tail+1) % data.length; // data.length对于数组的下标
  51. // int i = (this.tail + 1) % data.length;
  52. // 4. 队里的长度
  53. this.size++;
  54. }
  55. /**
  56. * 开始resize数组元素
  57. *
  58. * @param capacity
  59. */
  60. private void resize(int capacity) {
  61. // 开始进行数组的扩容
  62. // 1. 创建一个新的容器(默认多一个位置)
  63. E[] newData = (E[]) new Object[capacity + 1];
  64. // 2. 开始转移元素到新的容器
  65. for (int i = 0; i < size; i++) {
  66. int index = (head + i) % data.length;
  67. newData[i] = data[index];
  68. }
  69. // 添加完毕之后,开始回收之前的data,data里面的数据一直是最新的数据
  70. this.data = newData; // jvm的垃圾回收机制会自动回收内存data
  71. // 3. 添加完毕
  72. this.head = 0;
  73. // 4. 指向尾部
  74. this.tail = size;
  75. }
  76. @Override
  77. public Object dequeue() {
  78. // 1. 先来看下队列中有没有元素数据
  79. if (this.size == 0)
  80. throw new IllegalArgumentException("Empty queue cannot dequeue!");
  81. // 2. 开始看一下内存的大小
  82. Object ret = this.data[head];
  83. // 3. 开始删除数据
  84. this.data[head] = null;
  85. // TODO 注意点:直接使用+1而不是使用++
  86. // 4. 开始向后移动, 为了防止出错,尽量使用 this.head+1来进行操作,而不是使用 this.haad++, 否则可能会出现死循环的情况【特别注意!!!!!!!!!!!!】
  87. this.head = (this.head+1) % data.length;
  88. // 5. 计算新的容量大小
  89. this.size--;
  90. // 6. 开始进行缩容操作, d当容量为1, size为0的时候,就不需要缩小容量、、
  91. if (this.size == this.getCapacity() / 4)
  92. // 缩小为原来的一半大小的容量
  93. resize(this.getCapacity() / 2);
  94. // 获取出队列的元素
  95. return ret;
  96. }
  97. @Override
  98. public Object getFront() {
  99. // 由于front的位置一直是队列的第一个元素,直接返回即可
  100. if (this.size == 0)
  101. throw new IllegalArgumentException("Empty queue cannot get element!");
  102. return this.data[head];
  103. }
  104. @Override
  105. public String toString() {
  106. StringBuilder stringBuilder = new StringBuilder();
  107. stringBuilder.append(String.format("Array : size=%d, capacity=%d; ", size, getCapacity()));
  108. stringBuilder.append("LoopQueue head [");
  109. // 第一个元素在head的位置,最后一个元素在tail-1的位置
  110. // 注意循环队列的遍历方式,是一个循环
  111. for (int i = this.head; i != tail; i = (i+1) % data.length) {
  112. stringBuilder.append(data[i]);
  113. // 只要不是最后一个元素的话, 由于循环条件中已经规定了i!=tail, 因此这里是不能直接这样来判断的
  114. if ((i+1)%data.length == this.tail) {
  115. // 此时就是最后一个元素
  116. } else {
  117. stringBuilder.append(", ");
  118. }
  119. }
  120. stringBuilder.append("] tail");
  121. return stringBuilder.toString();
  122. }
  123. }

3.使用数组实现一个循环队列(不维护size变量)

  1. /**
  2. * 使用数组实现一个循环队列的数据结构
  3. */
  4. public class WithoutSizeLoopQueue<E> implements Queue<E> {
  5. private E[] data;
  6. private int head;
  7. private int tail;
  8. public WithoutSizeLoopQueue(int capacity) {
  9. // 泛型不能直接被实例化, 由于循环队列默认会空出来一个位置,这里需要注意
  10. this.data = (E[]) new Object[capacity + 1];
  11. this.head = 0;
  12. this.tail = 0;
  13. }
  14. public WithoutSizeLoopQueue() {
  15. this(10);
  16. }
  17. @Override
  18. public int getSize() {
  19. // 计算数组的元素个数
  20. if (tail >= head) {
  21. return tail - head;
  22. } else {
  23. int offSet = head - tail;
  24. return this.data.length - offSet;
  25. }
  26. }
  27. @Override
  28. public int getCapacity() {
  29. return data.length - 1;
  30. }
  31. @Override
  32. public void enqueue(E value) {
  33. // 开始进入队列
  34. // 1. 先来看下队列有没有满
  35. if ((tail + 1) % data.length == head)
  36. // 开始扩大容量
  37. resize(2 * getCapacity());
  38. // 2. 开始进入队列
  39. data[tail] = value;
  40. // 3. 开始移动下标
  41. tail++;
  42. // 4. 开始更新容量【没必要】
  43. }
  44. public void resize(int newCapacity) {
  45. // 1. 更新为新的容量
  46. E[] newData = (E[]) new Object[newCapacity + 1];
  47. // 2. 开始转移数据到新的数组中去
  48. for (int i = 0; i < getSize(); i++) {
  49. newData[i] = data[(i + head) % data.length];
  50. }
  51. // 3. 销毁原来的数据信息
  52. data = newData;
  53. // 【错误警告】bug:移动到新的这个数组里面之后,需要重新改变head和tail的位置【bug】
  54. tail = getSize(); // 尾部节点始终指向最后一个元素的后面一个位置, 此时如果直接使用getSize实际上获取到的是上一次的元素的个数,而不是最新的元素的个数信息(需要放在前面,否在获取到的size就不是同一个了,特别重要)
  55. head = 0; // 头节点指向的是第一个元素
  56. // 4. 直接销毁
  57. newData = null;
  58. }
  59. @Override
  60. public E dequeue() {
  61. // 1.先来看下队列是否为空
  62. if (getSize() == 0)
  63. throw new IllegalArgumentException("Empty queue cannot dequeue!");
  64. // 2.开始出head位置的元素
  65. E value = data[head];
  66. // 3. 删除元素
  67. data[head] = null;
  68. // 4. 移动head
  69. head = (head+1) % data.length;
  70. // 此时需要进行一次判断
  71. if (getSize() == getCapacity() / 4 && getCapacity() / 2 != 0)
  72. resize(getCapacity() / 2);
  73. // 如果数组缩小容量之后,这里的
  74. return value;
  75. }
  76. @Override
  77. public E getFront() {
  78. return dequeue();
  79. }
  80. @Override
  81. public String toString(){
  82. // 开始遍历输出队列的元素
  83. StringBuilder stringBuilder = new StringBuilder();
  84. stringBuilder.append(String.format("LoopQueue: size = %d, capacity = %d\n", getSize(), getCapacity()));
  85. stringBuilder.append("front ");
  86. // 从head位置开始遍历
  87. for (int i = head; i != tail ; i = (i+1) % data.length) {
  88. stringBuilder.append(data[i]);
  89. if ((i + 1) % data.length != tail)
  90. stringBuilder.append(", ");
  91. }
  92. return stringBuilder.append(" tail").toString();
  93. }
  94. }

4. 使用链表实现一个队列

  1. /**
  2. * 使用链表实现的队列
  3. */
  4. public class LinkedListQueue<E> implements Queue<E>{
  5. // 这是一个内部类,只能在这个类的内部可以访问(用户不需要了解底层是如何实现的)
  6. private class Node {
  7. // 用于存储元素
  8. public E e;
  9. // 用于存储下一个节点
  10. public Node next;
  11. public Node(E e, Node next) {
  12. this.e = e;
  13. this.next = next;
  14. }
  15. public Node(E e) {
  16. this(e, null);
  17. }
  18. public Node() {
  19. this(null, null);
  20. }
  21. @Override
  22. public String toString() {
  23. return e.toString();
  24. }
  25. }
  26. // 定义队列需要的参数
  27. private Node head, tail;
  28. private int size;
  29. public LinkedListQueue(){
  30. this.head = null;
  31. this.tail = null;
  32. this.size = 0;
  33. }
  34. @Override
  35. public int getSize() {
  36. return this.size;
  37. }
  38. public boolean isEmpty(){
  39. return this.size == 0;
  40. }
  41. @Override
  42. public int getCapacity() {
  43. return 0;
  44. }
  45. /**
  46. * 入队
  47. * @param value
  48. */
  49. @Override
  50. public void enqueue(E value) {
  51. // 入队从尾部开始
  52. if (tail == null){
  53. // 1. 如果此时没有元素的话
  54. tail = new Node(value);
  55. head = tail;
  56. }
  57. else {
  58. // 2. 如果已经存在了元素,那么队列尾部肯定是不为空的,而是指向了一个空节点
  59. tail.next = new Node(value);
  60. // 移动尾节点
  61. tail = tail.next;
  62. }
  63. size++;
  64. }
  65. /**
  66. * 出队
  67. * @return
  68. */
  69. @Override
  70. public E dequeue() {
  71. if (isEmpty())
  72. throw new IllegalArgumentException("Empty queue cannot dequeue!");
  73. // 1. 存储出队的元素
  74. Node retNode = head;
  75. // 2.head节点下移动
  76. head = head.next;
  77. // 3.删除原来的头节点
  78. retNode.next = null; // 实际上删除的是这个节点的数据域
  79. // 如果删除了最后一个元素之后
  80. if (head == null)
  81. tail = null;
  82. size--;
  83. return retNode.e;
  84. }
  85. @Override
  86. public E getFront() {
  87. if (isEmpty())
  88. throw new IllegalArgumentException("Empty queue cannot dequeue!");
  89. return head.e;
  90. }
  91. @Override
  92. public String toString(){
  93. StringBuilder stringBuilder = new StringBuilder();
  94. Node cur = head;
  95. stringBuilder.append("head:");
  96. // 1. 第一种循环遍历的方式, 注意这里判断的是每一次的cur是否为空, 而不是判断cur.next, 否则第一个元素是不能打印输出的
  97. while (cur != null) {
  98. stringBuilder.append(cur + "->");
  99. // 继续向后移动节点
  100. cur = cur.next;
  101. }
  102. stringBuilder.append("NULL tail");
  103. return stringBuilder.toString();
  104. }
  105. }

5. 使用一个带有虚拟头结点的链表实现一个队列

  1. /**
  2. * 带有虚拟头结点的链表实现的队列
  3. */
  4. public class DummyHeadLinkedListQueue<E> implements Queue<E> {
  5. // 这是一个内部类,只能在这个类的内部可以访问(用户不需要了解底层是如何实现的)
  6. private class Node {
  7. // 用于存储元素
  8. public E e;
  9. // 用于存储下一个节点
  10. public Node next;
  11. public Node(E e, Node next) {
  12. this.e = e;
  13. this.next = next;
  14. }
  15. public Node(E e) {
  16. this(e, null);
  17. }
  18. public Node() {
  19. this(null, null);
  20. }
  21. @Override
  22. public String toString() {
  23. return e.toString();
  24. }
  25. }
  26. // 定义一个虚拟头结点
  27. private Node dummyHead, tail;
  28. private int size;
  29. public DummyHeadLinkedListQueue(){
  30. this.dummyHead = new Node(null, null);
  31. this.tail = null;
  32. this.size = 0;
  33. }
  34. @Override
  35. public int getSize() {
  36. return this.size;
  37. }
  38. public Boolean isEmpty(){
  39. return this.size == 0;
  40. }
  41. @Override
  42. public int getCapacity() {
  43. throw new IllegalArgumentException("LinkedList cannot getCapacity!");
  44. }
  45. @Override
  46. public void enqueue(E value) {
  47. // 开始入队列(从队列的尾部进行入队列)
  48. // 1. 构造插入的节点
  49. Node node = new Node(value);
  50. // 2. 尾部插入节点 //bug : 由于默认的时候 tail为null,此时如果直接访问tail.next 是错误的
  51. if (tail == null) {
  52. dummyHead.next = node;
  53. // 说明此时队列为空的
  54. tail = node;
  55. } else {
  56. tail.next = node;
  57. // 3. 尾部节点向后移动
  58. tail = tail.next;
  59. }
  60. // 4. 队列长度增加
  61. size++;
  62. }
  63. @Override
  64. public E dequeue() {
  65. // 元素出队列从链表的头部出去
  66. // 1. 获取头结点的位置
  67. Node head = dummyHead.next;
  68. // 缓存出队的元素
  69. Node retNode = head;
  70. // 2. 移动节点
  71. dummyHead.next = head.next;
  72. // 4. 更新容量
  73. size--;
  74. head = null;
  75. if (isEmpty())
  76. tail = null; // bug修复
  77. return (E) retNode;
  78. }
  79. @Override
  80. public E getFront() {
  81. return null;
  82. }
  83. @Override
  84. public String toString(){
  85. StringBuilder stringBuilder = new StringBuilder();
  86. stringBuilder.append("Queue head:");
  87. Node cur = dummyHead.next;
  88. while (cur != null){
  89. stringBuilder.append(cur + "->");
  90. cur = cur.next;
  91. }
  92. return stringBuilder.append("null tail").toString();
  93. }
  94. }

  

以上就是创建队列实现的5中方式,每种方式都有各自的特点,就做个简单总结吧!  

队列的时间复杂度分析:

 

+ 队列Queue[数组队列]
void enqueue(E) O(1) 均摊复杂度
E dequeue() O(n) 移出队列首部的元素,需要其他元素向前补齐
E getFront() O(1)
void dequeue() O(1)
Int getSize() O(1)
bool isEmpty() O(1)
+ 循环队列
void enqueue(E) O(1) 均摊复杂度
E dequeue() O(1)
E getFront() O(1)
void dequeue() O(1)
Int getSize() O(1)
bool isEmpty() O(1)

以上就是对于队列实现的几种方式的总结了。

 

  

  

转载于:https://www.cnblogs.com/52tech/p/9980323.html

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/空白诗007/article/detail/960595
推荐阅读
相关标签
  

闽ICP备14008679号