数组队列

数组队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。遵循先进先出(FIFO)原则。

【数组队列代码实现】

先定义一个Queue接口

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

循环队列

百度百科:

  • 循环队列就是将队列存储空间的最后一个位置绕到第一个位置,形成逻辑上的环状空间,供队列循环使用。

  • 在循环队列结构中,当存储空间的最后一个位置已被使用而再要进入队运算时,只需要存储空间的第一个位置空闲,便可将元素加入到第一个位置,即将存储空间的第一个位置作为队尾。

  • 循环队列可以更简单防止伪溢出的发生,但队列大小是固定的。

  • 在循环队列中,当队列为空时,有 front == tail,而当所有队列空间全占满时,也有 front == tail

为了区别这两种情况,规定循环队列最多只能有 MaxSize-1个队列元素,当循环队列中只剩下一个空存储单元时,队列就已经满了。因此,队列判空的条件是 front == tail,而队列判满的条件是 front ==(tail+1) % MaxSize

​ 图1.空队列

​ 图2.插入元素,tail++

​ 图3.删除队头元素front++

​ 图4. 继续队尾添加元素

​ 图5. 第一个位置空闲,便可将元素加入到第一个位置

​ 图6.计算循环索引

  1. public LoopQueue<E> implements Queue<E> {
  2. private E[] data;
  3. private int front, tail;
  4. private int size;
  5. public LoopQueue(int capacity) {
  6. data = new Object[capacity + 1]; // 循环队列中空闲了一个空间
  7. front = 0;
  8. tail = 0;
  9. size = 0;
  10. }
  11. public LoopQueue() {
  12. this(10);
  13. }
  14. public int getCapacity() {
  15. return data.length - 1;
  16. }
  17. @Override
  18. public boolean isEmpty() {
  19. return front == tail;
  20. }
  21. @Override
  22. public int getSize() {
  23. return size;
  24. }
  25. @Override
  26. public void equeue(E e) {
  27. // 首先判断队列是否满了
  28. if (front == (tail + 1) % data.length) {
  29. // 扩容
  30. resize(getCapacity() * 2);
  31. }
  32. data[tail] = e;
  33. tail = (tail + 1) % data.length;
  34. size++;
  35. }
  36. @Override
  37. public E dequeue() {
  38. if (isEmpty()) {
  39. throw new IllegalArgumentException("Cannot dequeue an empty queue.");
  40. }
  41. E ret = data[front];
  42. data[front] = null;
  43. front = (front + 1) % data.length;
  44. size--;
  45. // 缩容
  46. if (size == getCapacity() / 4 && getCapacity() / 2 != 0) {
  47. resize(getCapacity() / 2)
  48. }
  49. return ret;
  50. }
  51. public void resize(int newCapacity) {
  52. E[] newData = (E[]) new Object[newCapacity + 1];
  53. for (int i = 0; i < size; i++) {
  54. newData[i] = data[(i + front) % data.length];
  55. }
  56. data = newData;
  57. front = 0;
  58. tail = size;
  59. }
  60. }

数组队列和循环队列比较

  • 复杂度

    • 循环队列:E dequeue(),O(1)
    • 数组队列:E dequeue(),O(n)

使用动态数组实现栈和队列

使用栈实现队列

使用队列实现栈

链表

链表的几个要点:

  1. 虚拟头结点,能解决很多问题(有时候短的链表,就会出现一些我们逻辑操作越界的问题)。
  2. 要注意先处理后面的结点,避免改指针时找不到它了,必要时用临时指针存着。
  3. 用好双指针!

链表的实现

  1. public class LinkedList<E> {
  2. public class Node {
  3. public E e;
  4. public Node next;
  5. public Node(E e, Node next) {
  6. this.e = e;
  7. this.next = next;
  8. }
  9. public Node(E e) {
  10. this(e, null);
  11. }
  12. public Node() {
  13. this(null, null);
  14. }
  15. @Override
  16. public String toString() { return e.toString(); }
  17. }
  18. private Node dummyHead;
  19. private int size;
  20. public LinkedList() {
  21. dummyHead = new Node(null, null); // 虚拟头结点
  22. size = 0;
  23. }
  24. public int getSize() { return size; }
  25. public boolean isEmpty() { return size== 0;}
  26. public void add(int index, E e) {
  27. if (index < 0 || index > size) {
  28. throw new IllegalArgumentException("Index error.");
  29. }
  30. Node prev = dummyHead;
  31. for (int i = 0; i < index - 1; i++)
  32. prev = prev.next;
  33. Node node = new Node(e);
  34. node.next = prev.next;
  35. prev.next = node;
  36. // prev.next = new Node(e, prev.next);
  37. size++;
  38. }
  39. public void addFirst(E e) {
  40. add(0, e);
  41. }
  42. public void addLast(E e) { add(size, e); }
  43. public E remove(int index) {
  44. if (index < 0 || index >= size) {
  45. throw new IllegalArgumentException("Index error.");
  46. }
  47. Node prev = dummyHead;
  48. // 找到待删除节点的前一结点
  49. for (int i = 0; i < index; i++)
  50. prev = prev.next;
  51. Node retNode = prev.next;
  52. prev.next = retNode.next;
  53. retNode.next = null;
  54. size --;
  55. return retNode.e;
  56. }
  57. public E removeFirst() {
  58. return remove(0);
  59. }
  60. public E removeLast() {
  61. return remove(size - 1);
  62. }
  63. public void set(int index, E e) {
  64. if (index < 0 || index >= size)
  65. throw new IllegalArugmentException("Index error");
  66. Node cur = dummyHead.next;
  67. for (int i = 0; i < index; i++)
  68. cur = cur.next;
  69. cur.e = e;
  70. }
  71. public boolean contains(E e) {
  72. Node cur = dummyHead.next;
  73. while (cur != null) {
  74. if (cur.next != null & cur.e.equals(e))
  75. return true;
  76. cur = cur.next;
  77. }
  78. return false;
  79. }
  80. @Override
  81. public String toString() {
  82. StringBuilder sb = new StringBuilder();
  83. Node cur = dummyHead.next;
  84. while (cur != null) {
  85. sb.append(cur + "->");
  86. cur = cur.next;
  87. }
  88. sb.append("NULL");
  89. return sb.toString();
  90. }
  91. }

用链表实现栈

Stack.java

  1. public interface Stack<E> {
  2. int getSize();
  3. boolean isEmpty();
  4. void push(E e);
  5. E pop();
  6. E peek();
  7. }

LinkedListStack.java

  1. public LinkedListStack<E> implements Stack<E> {
  2. private LinkedList<E> list; // 先声明一个链表
  3. public LinkedListStack() {
  4. list = new LinkedList<>();
  5. }
  6. @Override
  7. public int getSize() { return list.getSize(); }
  8. @Override
  9. public boolean isEmpty() { return list.isEmpty(); }
  10. @Override
  11. public void push(E e) {
  12. list.addFirst(e); // 插入栈顶
  13. }
  14. @Override
  15. public E pop() {
  16. return list.removeFirst(); // 弹出栈顶元素
  17. }
  18. @Override
  19. public E peek() {
  20. return list.getFirst(); // 查看栈顶元素
  21. }
  22. }

改进链表

  • 从head端删除元素,从tail端插入元素
  • 没有dummyHead,需注意链表为空的情况
  1. // LinkedListQueue.java
  2. public class LinkedListQueue<E> implements Queue<E> {
  3. // 定义一个节点
  4. private class Node {
  5. public E e;
  6. public Node next;
  7. public Node(E e, Node next) {
  8. this.e = e;
  9. this.next = next;
  10. }
  11. public Node(E e) {
  12. this(e, null);
  13. }
  14. public Node() {
  15. this(null, null);
  16. }
  17. }
  18. private Node head, tail;
  19. private size;
  20. public LinkedListQueue() {}
  21. @Override
  22. public int getSize() { return size; }
  23. @Override
  24. public boolean isEmpty() { return size==0; }
  25. @Override
  26. public void enqueue(E e) {
  27. if (tail == null) {
  28. tail = new Node(e);
  29. head = tail;
  30. } else {
  31. tail.next = new Node(e);
  32. tail = tail.next;
  33. }
  34. size++;
  35. }
  36. @Override
  37. public E dequeue() {
  38. if (isEmpty()) throw new IllegalArgumentException("queue is empty!");
  39. Node retNode = head;
  40. head = head.next;
  41. retNode.next = null;
  42. if (head == null)
  43. tail = null;
  44. size--;
  45. return retNode.e;
  46. }
  47. @Override
  48. public E getFront() {
  49. if (isEmpty()) throw new IllegalArgumentException("queue is empty!");
  50. return head.e;
  51. }
  52. }

链表的性能问题

虽然只在链表表头添加元素,时间复杂度O(1),同时,使用链表不需要resize,直觉上链表性能更好;

But,实际上,当数据量达一定程度,其性能是更差的,因为:

  • 对于链表,每添加一个元素,都需要重新创建一个Node类对象(需要进行一次new内存操作),这是非常慢的。

为什么即使有resize,对大规模数据,动态数组还是快于链表?

  • 对动态数组,一方面,每次resize容量倍增,但对大规模数据,实际上触发resize次数非常少的

  • resize的过程,一次申请一大片内存空间。链表每次申请一个空间

  • 申请一次10万的空间远远快于10万次1的空间。

  • 相较于堆内存空间的操作,动态数组的resize过程虽然还需赋值(旧数组元素拷贝到新数组),但该拷贝过程是远远快于对内存的操作的

📚 队列-DS笔记的更多相关文章

  1. 单调栈&单调队列学习笔记!

    ummm,,,都是单调系列就都一起学了算了思想应该都差不多呢qwq 其实感觉这俩没有什么可说的鸭QAQ就是维护一个单调的东西,区别在于单调栈是一段进一段出然后单调队列是一段进另一段出?没了 好趴辣重点 ...

  2. 📚 选择排序和插入排序区别-DS笔记

    选择排序法 A[i...n)未排序,A[0...i)已排序 A[i...n]中最小值要放到A[i]的位置 复杂度 \(O(n^2)\) 第一层循环n次 第二层循环:i=0,n次:i=1,n-1次... ...

  3. the little schemer 笔记(8)

    第八章 lambda the ultimate 还记得我们第五章末的rember和insertL吗 我们用equal?替换了eq? 你能用你eq?或者equal?写一个函数rember-f吗 还不能, ...

  4. Java:阻塞队列

    Java:阻塞队列 本笔记是根据bilibili上 尚硅谷 的课程 Java大厂面试题第二季 而做的笔记 1. 概述 概念 队列 队列就可以想成是一个数组,从一头进入,一头出去,排队买饭 阻塞队列 B ...

  5. 【Python】分布式任务队列Celery使用参考资料

    Python-Celery Homepage | Celery: Distributed Task Queue User Guide - Celery 4.0.2 documentation Task ...

  6. 终于懂了:WM_PAINT 与 WM_ERASEBKGND(三种情况:用户操作,UpdateWindow,InvalidateRect产生的效果并不相同),并且用Delphi代码验证 good

    一直对这两个消息的关系不是太了解,借重新深刻学习windows编程的机会研究一番. 1)当窗口从无效变为有效时,比方将部分覆盖的窗口恢复时会重绘窗口时:程序首先会通过发送其他消息调用DefWindow ...

  7. Linux进程间通信IPC学习笔记之消息队列(SVR4)

    Linux进程间通信IPC学习笔记之消息队列(SVR4)

  8. JavaSE中Collection集合框架学习笔记(2)——拒绝重复内容的Set和支持队列操作的Queue

    前言:俗话说“金三银四铜五”,不知道我要在这段时间找工作会不会很艰难.不管了,工作三年之后就当给自己放个暑假. 面试当中Collection(集合)是基础重点.我在网上看了几篇讲Collection的 ...

  9. 课堂笔记及知识点----栈和队列(2018/10/24(am))

    栈: Stack<int>  xt=new Stack<int>() ; 先进后出,后进先出,水杯结构,顺序表类似 常用方法:   .pop---->出栈,弹栈     ...

随机推荐

  1. Pytorch技法:继承Subset类完成自定义数据拆分

    我们在<torch.utils.data.DataLoader与迭代器转换>中介绍了如何使用Pytorch内置的数据集进行论文实现,如torchvision.datasets.下面是加载内 ...

  2. 用python的turtle作图(一)静态图

    最近,花了点时间,用python画图. 主要包括三部分,简单的静态图形,复杂的组合图形,图形动画. (一)画静态图形 长方形, 圆, 三角形, 平行四边形, 五角星 (二)图形的组合 笑脸, 国旗, ...

  3. Involuting Bunny! (2021.9)

      文化课就很掉头发,文科都能学好我还怕竞赛?(   大概从"刷的题的题解"推广为"所有做的题的题解"吧,兔子比较懒,这样写题解轻松一些.   Gym10305 ...

  4. CPU优化之平均负载率之辅助工具

    前面介绍了平均负载均衡的一些内容,那实际应用中如何查看,分析性能瓶颈呢?下面介绍相关的辅助工具. 一.stress stress是Linux 系统压力测试工具,其通过异常进程模拟平均负载升高的场景(前 ...

  5. 面渣逆袭:二十二图、八千字、二十问,彻底搞定MyBatis!

    大家好,我是老三,面渣逆袭系列继续,这节我们的主角是MyBatis,作为当前国内最流行的ORM框架,是我们这些crud选手最趁手的工具,赶紧来看看面试都会问哪些问题吧. 基础 1.说说什么是MyBat ...

  6. 『无为则无心』Python面向对象 — 58、类方法和静态方法

    目录 1.实例方法 2.类方法 (1)类方法特点 (2)类方法使用场景 3.静态方法 (1)静态方法特点 (2)静态方法使用场景 1.实例方法 实例方法既可以调用静态方法也可以调用类方法. # 定义一 ...

  7. JsonResponse类的使用、form表单上传文件补充、CBV和FBV、HTML的模板语法之传值与过滤器

    昨日内容回顾 Django请求生命周期 # 1.浏览器发起请求 到达Django的socket服务端(web服务网关接口) 01 wsgiref 02 uwsgi + nginx 03 WSGI协议 ...

  8. github push时提示Username for 'https://github.com' 解决办法

    问题 github push时在输入账号密码后仍提示:Username for 'https://github.com',需要进一步输入账号密码. 解决方案 注意这里的账号密码并不是github的登录 ...

  9. 深入理解Cache工作原理

    内容来源:https://zhuanlan.zhihu.com/p/435031232 内容来源:https://zhuanlan.zhihu.com/p/102293437 本文主要内容如下,基本涉 ...

  10. 动态语言运行时(DLR)

    前言 为了让C#.Visual Basic等.NET编程语言能具备动态编程语言的特性,.NET 4.0引入了一个"DLR(Dynamic Language Runtime:动态语言运行时)& ...