队列介绍

  • 队列是一个有序列表,可以用数组或是链表来实现。
  • 遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出

    数组模拟队列

    思路

    2020071421562057.png

  • 队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如上图图, 其中 maxSize 是该队列的最大容量。

  • 因为队列的输出、输入是分别从前后端来处理,因此需要两个变量 front及 rear分别记录队列前后端的下标,front 会随着数据输出而改变,而 rear则是随着数据输入而改变。
  • front 指向队列头部,分析出front是指向队列头的前一个位置
  • rear 指向队列尾部,分析出rear是指向队列尾部的数据(即队列最后一个数据)
  • 将尾指针往后移:rear+1
  • 当front == rear ,队列为空
  • 若尾指针 rear 小于队列的最大下标 maxSize-1,则将数据存入 rear所指的数组元素中,否则无法存入数据。 当rear == maxSize - 1,说明队列已满。

    示例代码

    ```java package com.concise.demo.data_structure.queue;

import java.util.Scanner;

/**

  • 使用数组模拟一个队列
  • @author shenguangyang
  • @date 2022-02-27 9:54 */ public class ArrayQueue { public static void main(String[] args) {

    1. boolean loop = true;
    2. System.out.println("a [add], 添加数据");
    3. System.out.println("g [get], 获取数据");
    4. System.out.println("sa [showAll], 展示全部数据");
    5. System.out.println("sh [showHeader], 展示头部数据");
    6. System.out.println("e [exit], 退出");
    7. ArrayQueue queue = new ArrayQueue(4);
    8. Scanner scanner = new Scanner(System.in);
    9. while (loop) {
    10. String key = scanner.nextLine();
    11. switch(key) {
    12. case "a":
    13. try {
    14. System.out.print("请输入一个数字: ");
    15. int data = scanner.nextInt();
    16. queue.add(data);
    17. } catch (Exception e) {
    18. System.err.println(e.getMessage());
    19. }
    20. break;
    21. case "g":
    22. try {
    23. System.out.println(queue.get());
    24. } catch (Exception e) {
    25. System.err.println(e.getMessage());
    26. }
    27. break;
    28. case "sa":
    29. try {
    30. queue.showAll();
    31. } catch (Exception e) {
    32. System.err.println(e.getMessage());
    33. }
    34. break;
    35. case "sh":
    36. try {
    37. queue.showHeader();
    38. } catch (Exception e) {
    39. System.err.println(e.getMessage());
    40. }
    41. break;
    42. case "e":
    43. loop = false;
    44. break;
    45. default:
    46. break;
    47. }
    48. }
    49. System.out.println("程序退出了");

    }

    private final int[] arr; /**

    • 数组队列头部 / private int front; /*
    • 数组队列尾部 / private int real; /*
    • 表示数组队列的最大容量 */ private final int maxSize;

      public ArrayQueue(int maxSize) { this.maxSize = maxSize; this.arr = new int[maxSize]; // 指向队列头部,front是指向队列头部数据的前一个位置 this.front = -1; // 指向队列尾部,rear是指向队列尾部数据 this.real = -1; }

      /**

    • 判断队列是否已满
    • @return */ public boolean isFull() { // rear队列尾部数据==最大容量,说明队列已满 return this.real == this.maxSize - 1; }

      /**

    • 判断队列是否为空
    • @return */ public boolean isEmpty() { // 队列头部指针==队列尾部指针,说明队列为空 return this.real == this.front; }

      public void add(int data) { if (this.isFull()) {

      1. throw new RuntimeException("队列已经满了");

      } // 队列尾部指针向后移 this.real = this.real + 1; this.arr[this.real] = data; }

      public int get() { if (this.isEmpty()) {

      1. throw new RuntimeException("队列为空");

      } // 队列头部指针向后移动 this.front = this.front + 1; return this.arr[this.front]; }

      public void showAll() { if (this.isEmpty()) {

      1. throw new RuntimeException("队列为空");

      } for (int i = 0; i < this.arr.length; i++) {

      1. System.out.printf("\tarr[%d] = %d\n", i, this.arr[i]);

      } }

      public void showHeader() { if (this.isEmpty()) {

      1. throw new RuntimeException("队列为空");

      } System.out.printf(“\tarr[%d] = %d\n”, this.front + 1, this.arr[this.front + 1]); } } ```

测试结果如下

  1. a [add], 添加数据
  2. g [get], 获取数据
  3. sa [showAll], 展示全部数据
  4. sh [showHeader], 展示头部数据
  5. e [exit], 退出
  6. a
  7. 请输入一个数字: 10
  8. sa
  9. arr[0] = 10
  10. arr[1] = 0
  11. arr[2] = 0
  12. arr[3] = 0
  13. sh
  14. arr[0] = 10
  15. a
  16. 请输入一个数字: 20
  17. sa
  18. arr[0] = 10
  19. arr[1] = 20
  20. arr[2] = 0
  21. arr[3] = 0
  22. sh
  23. arr[0] = 10
  24. a
  25. 请输入一个数字: 30
  26. a
  27. 请输入一个数字: 40
  28. a
  29. 请输入一个数字: 50
  30. 队列已经满了
  31. sa
  32. arr[0] = 10
  33. arr[1] = 20
  34. arr[2] = 30
  35. arr[3] = 40
  36. g
  37. 10
  38. g
  39. 20
  40. g
  41. 30
  42. sh
  43. arr[3] = 40
  44. g
  45. 40
  46. sh
  47. 队列为空
  48. e
  49. 程序退出了
  50. Process finished with exit code 0

问题分析并优化思路

问题:目前数组使用一次就不能用,没有达到复用的效果
优化思路:将这个数组使用算法,改进成一个环形的队列 取模的方式

当数据从队列全部取出之后,再添加数据, 不能添加, 说明数组使用一次就不能再次使用,没有达到复用效果

数组模拟环形队列

实现:让数组达到复用的效果,即:当我们从数组队列中取出了数据,那取出数据后后这个空间可以再次使用

思路分析

当rear指针指向maxsize - 1时,也就是当rear = maxsize - 1 时,需要判断rear的前面是否有空闲空间,也就是说front是否产生了变化并且已经不在初始的那个位置了
20191210174837654.png
20191210174845731.png

思路如下:

  • front的含义做调整 :front由原来的指向队列头的前一个位置调整为现在的front指向了队列头
  • 队列头:就是队列中第一个数据所在的位置
  • 即:queueArr[front]就是获取的队列中的第一个数据

20191210174857295.png

说明:

  • 原来front的初始值为 - 1 现在 front 的初始值为0
  • 原来的front是不包含队列头的,现在的front是包含队列头的

  • rear的含义做调整:rear从原来指向队列的最后一个数据调整为了现在的rear指向队列的最后一个数据的前一个位置

  • 调整rear的目的:预留一个空间作为约定

20191210174857295.png

说明:

  • 原来的rear的初始值为 - 1,现在的rear的初始值为0
  • 原来的rear包含队列的最后一个数据,现在的rear是不包含队列的最后一个数据。
  • 预留的空间是动态变化的,预留空间始终都在rear指向的最后一个数据的后一个位置
  • rear和front含义发生调整后,判断队列满的条件是( rear + 1 )% maxsize = front
  • 为什么要取模? 答:因为rear可能是一直增长的,从而达到数组空间复用的效果, 如果不进行取模会出现数组越界情况
  • 原先队列满的条件是rear = maxsize - 1 因为 原先没有考虑数组空间的复用

20191210174857295.png

  1. 判断队列为空的条件是rear = front
    20191210174857295.png

经过以上分析

  • 当rear和front含义发生调整后,队列中有效数据的个数 是 (rear + maxsize - front) % maxsize
  • 为什么要取模?答:因为是环形队列,rear有可能指到队列最前面。
  • 为什么要加上maxsize?
    • 因为是环形队列,rear有可能指到队列最前面, 所以 rear - front 很有可能为负数, 这时候 (rear - front) + maxsize就是有效个数, 如果不为负数, 需要进行 % maxsize, 才能保证结果为有效个数
  • 打个比方,当rear = 0,maxsize = 4 ,front = 0时,该队列中有多少个有效的数据?

20191210174857295.png
分析:(0 + 3 - 0)% 3 = 0,该队列中有效的数据个数为0个,因为rear = front ,则证明是空队列

代码实现

  1. package com.concise.demo.data_structure.queue;
  2. import java.util.Scanner;
  3. /**
  4. * 使用数组模拟一个环形队列
  5. * @author shenguangyang
  6. * @date 2022-02-27 9:54
  7. */
  8. public class CirCleArrayQueue {
  9. public static void main(String[] args) {
  10. boolean loop = true;
  11. System.out.println("a [add], 添加数据");
  12. System.out.println("g [get], 获取数据");
  13. System.out.println("sa [showAll], 展示全部数据");
  14. System.out.println("sh [showHeader], 展示头部数据");
  15. System.out.println("e [exit], 退出");
  16. CirCleArrayQueue queue = new CirCleArrayQueue(4);
  17. Scanner scanner = new Scanner(System.in);
  18. while (loop) {
  19. String key = scanner.nextLine();
  20. switch(key) {
  21. case "a":
  22. try {
  23. System.out.print("请输入一个数字: ");
  24. int data = scanner.nextInt();
  25. queue.add(data);
  26. } catch (Exception e) {
  27. System.err.println(e.getMessage());
  28. }
  29. break;
  30. case "g":
  31. try {
  32. System.out.println(queue.get());
  33. } catch (Exception e) {
  34. System.err.println(e.getMessage());
  35. }
  36. break;
  37. case "sa":
  38. try {
  39. queue.showAll();
  40. } catch (Exception e) {
  41. System.err.println(e.getMessage());
  42. }
  43. break;
  44. case "sh":
  45. try {
  46. queue.showHeader();
  47. } catch (Exception e) {
  48. System.err.println(e.getMessage());
  49. }
  50. break;
  51. case "e":
  52. loop = false;
  53. break;
  54. default:
  55. break;
  56. }
  57. }
  58. System.out.println("程序退出了");
  59. }
  60. private final int[] arr;
  61. /**
  62. * front 调整后的含义为: 指向队列头(即:队列中的第一个数据),front的初始值为 0
  63. */
  64. private int front;
  65. /**
  66. * rear 调整后的含义为:指向队列中的最后一个数据的前一个位置 ,rear 的初始值为0
  67. */
  68. private int real;
  69. /**
  70. * maxsize 表示队列的最大容量,队列中的数据是存放在数组中的
  71. */
  72. private final int maxSize;
  73. public CirCleArrayQueue(int maxSize) {
  74. this.maxSize = maxSize;
  75. this.arr = new int[maxSize];
  76. this.front = 0;
  77. this.real = 0;
  78. }
  79. /**
  80. * 判断队列是否已满
  81. */
  82. public boolean isFull() {
  83. return (this.real + 1) % maxSize == this.front;
  84. }
  85. /**
  86. * 判断队列是否为空
  87. */
  88. public boolean isEmpty() {
  89. return this.real == this.front;
  90. }
  91. public void add(int data) {
  92. if (this.isFull()) {
  93. throw new RuntimeException("队列已经满了");
  94. }
  95. this.arr[this.real] = data;
  96. this.real = (this.real + 1) % this.maxSize;
  97. }
  98. public int get() {
  99. if (this.isEmpty()) {
  100. throw new RuntimeException("队列为空");
  101. }
  102. // 第一步 先把queueArr[front]对应的保存在一个临时变量中
  103. // 为什么要将queueArr[front]对应的保存在一个临时变量中? 因为 若直接返回的话,就没有往后移的机会了
  104. int value = this.arr[this.front];
  105. this.front = (this.front + 1) % this.maxSize;
  106. return value;
  107. }
  108. public void showAll() {
  109. if (this.isEmpty()) {
  110. throw new RuntimeException("队列为空");
  111. }
  112. for (int i = front; i < front + size(); i++) {
  113. System.out.printf("\tarr[%d] = %d\n", i % this.maxSize, this.arr[i % this.maxSize]);
  114. }
  115. }
  116. public void showHeader() {
  117. if (this.isEmpty()) {
  118. throw new RuntimeException("队列为空");
  119. }
  120. System.out.printf("\tarr[%d] = %d\n", this.front, this.arr[this.front]);
  121. }
  122. public int size() {
  123. return (this.real + this.maxSize - this.front) % this.maxSize;
  124. }
  125. }