• 超神之路 数据结构 2 —— Queue队列实现和循环队列和普通队列的性能比较


            接上一篇继续往下挖,在上一篇,我们实现了一个属于自己的动态数组。利用这个动态数组,我们来实现一个基于动态数组,一个属于自己的普通队列Queue。

            Queue 是一种它许我们从表的一段进行删除,表的另一端进行插入的线性表。可能也有人问啥叫线性表。

    1. 线性表:线性表是n个具有相同特性的数据元素的有限序列 (n>=0)。
    2. n是线性表的长度,n=0就是空表,n>0时,表通常记作(a1,a2,a3...a_n)
    3. 除了a1外,表中每个元素 a[i] 均有唯一的前驱 a[i-1];
    4. 除了an外,表中每个元素 a[i] 均有唯一的后继 a[i+1];
    5. 线性表在逻辑上是线性结构,

            认识完了Queue,我们再来认识下Java util包下,Doug Lea 实现的Queue接口各个方法的定义。然后我们尽可能向JDK的源码靠近。

    1. package java.util;
    2. public interface Queue extends Collection {
    3. //添加一个元素,成功时返回true, 没有空间则报一个非法状态异常。
    4. boolean add(E e);
    5. //也时添加元素,比add更友好,add只给你异常,不给你false,offer给你返false。
    6. boolean offer(E e);
    7. //检索并删除此队列的头。此方法与poll的不同之处在于,它在队列为空时抛出异常。
    8. E remove();
    9. //检索并删除此队列的头,如果此队列为空则返回null。
    10. E poll();
    11. //检索但不删除此队列的头。此方法与peek的不同之处在于,它在队列为空时抛出异常。
    12. E element();
    13. //检索但不删除此队列的头,如果此队列为空则返回null。
    14. E peek();
    15. }

            全部翻译了一遍,相信看出来了,add,offer都是添加元素,remove和poll也都是删除首元素,element和peek也都是查看首元素。唯一的区别是,前者给你异常,想停掉代码流;而后者给你null,让你自己处理。

            add,remove,element ===>均异常,offer,poll,peek ===> 均给null。

            这个接口还继承了 Collection 接口,Collection自然会对多种底层实现的集合容器实现一个统一的迭代器,迭代器这块1.8之后又加了一些实用defaut方法,这些就不关注了。主要关注下面2个方法:

     size方法,返回容器中元素个数,isEmpty方法判断是否为空。

            把以上抛异常的三个方法 add,remove,element 做个整合,我们推出以下接口,尽量来靠近JDK。

    1. package com.company.queue;
    2. /**
    3. * 队列
    4. *
    5. * @author wang da wei
    6. * 2021/11/7 0:24
    7. * @version 1.0.0
    8. */
    9. public interface Queue {
    10. /**
    11. * 入队
    12. * @param e
    13. */
    14. void add(E e);
    15. /**
    16. * 出队
    17. * @return 出队的元素
    18. */
    19. E remove();
    20. /**
    21. * 查看队首的元素。
    22. * @return 队首元素。
    23. */
    24. E element();
    25. /**
    26. * 是否是空的。
    27. * @return 是否为空。
    28. */
    29. boolean isEmpty();
    30. /**
    31. * 查看线性表中元素个数。
    32. * @return 元素个数
    33. */
    34. int size();
    35. }

    有了这种接口,我们就可以做2种实现了。先做一个普通的队列,再做一个循环队列

    基于上一篇的动态数组,我们很容易的就可以实现出一个FIFO的队列。代码如下:

    1. package com.company.queue.common;
    2. import com.company.array.DynamicArray;
    3. import com.company.queue.Queue;
    4. public class ArrayQueue implements Queue {
    5. private DynamicArray dynamicArray ;
    6. public ArrayQueue() {
    7. this.dynamicArray = new DynamicArray<>();
    8. }
    9. @Override
    10. public void add(E e) {
    11. //添加到尾部 即 arr[size]位置。
    12. dynamicArray.add(e,dynamicArray.getSize());
    13. }
    14. @Override
    15. public E remove() {
    16. //移除头部元素,并返回。
    17. return dynamicArray.remove(0);
    18. }
    19. @Override
    20. public E element() {
    21. //查看第一个元素。
    22. return dynamicArray.get(0);
    23. }
    24. @Override
    25. public boolean isEmpty() {
    26. //查看是否为空
    27. return dynamicArray.getSize() == 0;
    28. }
    29. @Override
    30. public int size() {
    31. //获取动态数组中实际元素的个数。
    32. return dynamicArray.getSize();
    33. }
    34. }

    再来一个循环队列的实现:

    1. package com.company.queue.loop;
    2. import com.company.queue.Queue;
    3. public class LoopQueue implements Queue {
    4. private E[] data;
    5. private int front, tail;
    6. private int size; // 思考:不用 size 变量,如何实现?
    7. //如果用户知道最多为循环队列传递多少元素的时候,可以直接传入一个capacity.
    8. public LoopQueue(int capacity) {
    9. data = (E[]) new Object[capacity + 1];
    10. front =0;
    11. tail = 0;
    12. size = 0 ;
    13. }
    14. public LoopQueue(){
    15. this(10);
    16. }
    17. public int getCapacity(){
    18. return data.length -1;
    19. }
    20. @Override
    21. public void add(E e) {
    22. if( (tail +1 )% data.length == front ){
    23. resize(getCapacity() * 2);
    24. }
    25. data[tail] =e;
    26. tail = (tail + 1) % data.length;
    27. size ++;
    28. }
    29. private void resize(int newCapacity) {
    30. E[] newData = (E[]) new Object[newCapacity +1];
    31. for(int i = 0 ;i< size ;i++){
    32. int old_index = (i + front) % data.length;
    33. newData[i] = data[old_index];
    34. }
    35. data = newData;
    36. front = 0;
    37. tail =size;
    38. }
    39. @Override
    40. public E remove() {
    41. if(isEmpty() ) throw new IllegalArgumentException("cannot dequeue from an empty queue .");
    42. E ret = data[front];
    43. data[front] = null;
    44. front = (front + 1) % data.length;
    45. size--;
    46. return ret;
    47. }
    48. @Override
    49. public E element() {
    50. if(isEmpty() ) throw new IllegalArgumentException("cannot dequeue from an empty queue .");
    51. return data[front];
    52. }
    53. @Override
    54. public boolean isEmpty() {
    55. return front == tail;
    56. }
    57. @Override
    58. public int size() {
    59. return size;
    60. }
    61. @Override
    62. public String toString() {
    63. StringBuilder sb = new StringBuilder(String.format("Queue : size/capacity = %d/%d \nfront [ ",size,getCapacity()));
    64. for(int i = 0;i != tail; i = (i+1)% data.length){
    65. sb.append(data[i]);
    66. //当前索引不是最后一个元素。
    67. if((i+1)%data.length != tail ){
    68. sb.append(" , ");
    69. }
    70. }
    71. sb.append(" ] tail ");
    72. return sb.toString();
    73. }
    74. public static void main(String[] args) {
    75. LoopQueue queue = new LoopQueue<>();
    76. for(int i = 0 ;i<20;i++){
    77. queue.add(i);
    78. System.out.println(queue);
    79. if(i%3 == 2){
    80. queue.remove();
    81. System.out.println(queue);
    82. }
    83. }
    84. }
    85. }

    两种队列的性能比较:

    1. package com.company.queue;
    2. import com.company.queue.common.ArrayQueue;
    3. import com.company.queue.loop.LoopQueue;
    4. import java.util.Random;
    5. public class MainQueueTest {
    6. public static void main(String[] args) {
    7. int opCount = 10_0000;
    8. ArrayQueue arrayQueue = new ArrayQueue<>();
    9. double arrayQueueTime = testQueue(arrayQueue, opCount);
    10. System.out.println(arrayQueueTime);
    11. LoopQueue loopQueue = new LoopQueue<>();
    12. double loopQueueTime = testQueue(loopQueue, opCount);
    13. System.out.println(loopQueueTime);
    14. }
    15. public static double testQueue(Queue queue,int opCount){
    16. long start = System.nanoTime();
    17. Random random = new Random();
    18. for(int i = 0 ;i
    19. queue.add(random.nextInt(Integer.MAX_VALUE));
    20. }
    21. for(int i = 0 ;i
    22. queue.remove();
    23. }
    24. long end = System.nanoTime();
    25. return (end- start)/10_0000_0000D; //纳秒 : 9个0. 不要忘了加D,或者 后缀 .0
    26. }
    27. }

            由于循环队列出队操作是O(1)的时间复杂度,所以当数据量越大的时候,一般有更好的性能优势。

  • 相关阅读:
    【leetcode】【剑指offer Ⅱ】027. 回文链表
    简易的慢SQL自定义告警实战经验(支持多数据源)
    Springboot使用webupload大文件分片上传(包含前后端源码)
    帝国cms判断隐藏字段值为空的内容信息,列表空白分页不显示怎么弄
    Java,controller类里面,不要用 String (或int)定义变量
    ListUtil java
    JavaScript Web APIs第一天笔记
    springcloud-config git配置源加载(部署公钥问题)
    2023工博会 | 上海添力网络营销公司 | 助力工业品线上推广
    【操作系统导论】Abstraction Process | 进程 | 虚拟化 | 进程API
  • 原文地址:https://blog.csdn.net/wdw18668109050/article/details/127944789