小言_互联网的博客

数组 - 数组模拟环形队列的几种思路和思考【附源码】

349人阅读  评论(0)

(所有源码均在 https://github.com/zongzhec/AlgoPractise

 

概述

首先,如果用数组模拟队列,至少需要两个指针——front 和 rear,算了,就叫start (后称S)和 end (后称E)吧。

S代表当前有效数据的头部,E代表尾部。(因为队列中的数据会被取出,所以显然头部不可能总是0)。

要素分析

虽然我们会遇到以下几种情况的搭配组合,但是他们在实现的逻辑上是等价的。

  1. S可以表示为头元素的前一个位置(初始值为-1);
  2. S也可以表示头元素当前位置(初始值为0);
  3. E可以表示末元素的位置;
  4. E也可以表示为末元素位置+1。

既然等价,那就先随便挑一个组合:S表示头元素当前位置,E表示为末元素位置+1

这样可以比较容易的理解为:S直接代表取元素时候的位置,E代表放元素时候的位置

源码

如果不考虑其他因素,码出来的源码差不多是这样的。(其他没列出的源码均在 https://github.com/zongzhec/AlgoPractise


  
  1. package foo.zongzhe.line.queue;
  2. /**
  3. * 用数组实现循环队列思路1:
  4. * start为头元素所在位置,end为末元素所在位置+1
  5. */
  6. public class ArrayQueueCircle1 extends ArrayQueue {
  7. /**
  8. * 创建队列构造器
  9. */
  10. public ArrayQueueCircle1(int queueSize) {
  11. super(queueSize);
  12. this.maxSize = queueSize;
  13. array = new int[maxSize];
  14. start = - 1; // 指向队列头部
  15. end = 0;
  16. arrayQueueDesc = "ArrayQueueCircle1";
  17. System.out.println(String.format( "%s initialized. start = %d, end = %d", arrayQueueDesc, start, end));
  18. }
  19. /**
  20. * 判断队列是否满
  21. */
  22. @Override
  23. public boolean isFull() {
  24. return start == end;
  25. }
  26. /**
  27. * 判断队列是否为空
  28. *
  29. * @return boolean 队列是否为空
  30. */
  31. @Override
  32. public boolean isEmpty() {
  33. return start == end - 1;
  34. }
  35. /**
  36. * 添加数据到队列
  37. *
  38. * @param element 数据
  39. */
  40. @Override
  41. public void addElement(int element) {
  42. if (isFull()) {
  43. System.out.println(String.format( "%s is full, no values are allowed to add", arrayQueueDesc));
  44. } else {
  45. // start = (start == -1)? 0: start;
  46. array[end] = element;
  47. end = (end + 1) % maxSize;
  48. System.out.println(String.format( "%s added one element %d. start = %d, end = %d", arrayQueueDesc, element, start, end));
  49. }
  50. }
  51. /**
  52. * 从队列中取出元素
  53. *
  54. * @return 元素的值
  55. */
  56. @Override
  57. public int takeElement() {
  58. if (isEmpty()) {
  59. throw new RuntimeException(String.format( "%s is empty, no values are able to retrieve", arrayQueueDesc));
  60. } else {
  61. int res = array[start];
  62. start = (start + 1) % maxSize;
  63. return res;
  64. }
  65. }
  66. /**
  67. * 遍历队列
  68. */
  69. @Override
  70. public void showElements() {
  71. if (isEmpty()) {
  72. System.out.println(String.format( "%s is empty, no values are able to show", arrayQueueDesc));
  73. } else {
  74. System.out.print( "Iterating " + arrayQueueDesc + ": ");
  75. for ( int i = start; i < end; i = (i + 1) % maxSize) {
  76. System.out.print(array[i] + " ");
  77. }
  78. System.out.println();
  79. }
  80. }
  81. }

 

我们再写一个Demo类来测试:


  
  1. package foo.zongzhe.line.queue;
  2. import java.util.ArrayList;
  3. /**
  4. * 使用多种方法数组来模拟队列,包括环形和非环形。
  5. */
  6. public class ArrayQueueDemo {
  7. static ArrayList<ArrayQueue> arrayQueues;
  8. public static void main(String[] args) {
  9. // 创建队列
  10. int queueSize = 5;
  11. initialArrayQueues(queueSize);
  12. // 判断是否为空
  13. checkIsEmpty();
  14. // 添加一个元素
  15. int[] values = { 3};
  16. addElements(values);
  17. showElements();
  18. // 再次判断是否为空
  19. checkIsEmpty();
  20. // 连续添加多个元素直至满元素
  21. int[] values2 = { 5, 2, 4, 5};
  22. addElements(values2);
  23. showElements();
  24. // 判断队列是否满了
  25. checkIsFull();
  26. // 取出多个元素
  27. takeElement( 2);
  28. // 再次添加一个元素,查看队列是否可以循环利用
  29. int[] values3 = { 3};
  30. addElements(values3);
  31. // 再次判断队列是否满了
  32. checkIsFull();
  33. // 展示全部元素
  34. showElements();
  35. }
  36. private static void initialArrayQueues(int queueSize) {
  37. arrayQueues = new ArrayList<>();
  38. // arrayQueues.add(new ArrayQueueNoCircle(queueSize)); // 加一个ArrayQueueNoCircle
  39. arrayQueues.add( new ArrayQueueCircle1(queueSize)); // 加一个ArrayQueueCircle1
  40. }
  41. private static void checkIsEmpty() {
  42. for (ArrayQueue arrayQueue : arrayQueues) {
  43. System.out.println(String.format( "%s isEmpty? %b", arrayQueue.arrayQueueDesc, arrayQueue.isEmpty()));
  44. }
  45. }
  46. private static void addElements(int[] elementValues) {
  47. for (ArrayQueue arrayQueue : arrayQueues) {
  48. for ( int elementValue : elementValues) {
  49. arrayQueue.addElement(elementValue);
  50. }
  51. }
  52. }
  53. private static void checkIsFull() {
  54. for (ArrayQueue arrayQueue : arrayQueues) {
  55. System.out.println(String.format( "%s isFull? %b", arrayQueue.arrayQueueDesc, arrayQueue.isFull()));
  56. }
  57. }
  58. private static void takeElement(int elementNumber) {
  59. for (ArrayQueue arrayQueue : arrayQueues) {
  60. for ( int i = 1; i <= elementNumber; i++) {
  61. int res = arrayQueue.takeElement();
  62. }
  63. }
  64. }
  65. private static void showElements() {
  66. for (ArrayQueue arrayQueue : arrayQueues) {
  67. arrayQueue.showElements();
  68. }
  69. }
  70. }

运行结果和思考

一旦运行,发现程序报错了,说是取元素的下标越界。

不难理解,因为我么初始化的时候S=-1,但是取元素时候显然是没有-1这个下标的。

怎么办?一个明显的思路就是但凡我在队列里面添加了第一个元素,我就立即把S重制成0


  
  1. /**
  2. * 添加数据到队列
  3. *
  4. * @param element 数据
  5. */
  6. @Override
  7. public void addElement(int element) {
  8. if (isFull()) {
  9. System.out.println(String.format( "%s is full, no values are allowed to add", arrayQueueDesc));
  10. } else {
  11. start = (start == - 1)? 0: start; // 重置S的值,以免下标为-1,造成越界
  12. array[end] = element;
  13. end = (end + 1) % maxSize;
  14. System.out.println(String.format( "%s added one element %d. start = %d, end = %d", arrayQueueDesc, element, start, end));
  15. }
  16. }

再跑一把,发现是另外一个Exception:明明有数据,但是取数据的时候说队列为空,无法取数据。

为什么呢?因为在判断队列是否为空的时候,我们检查的是S是否等于E。


  
  1. /**
  2. * 判断队列是否为空
  3. *
  4. * @return boolean 队列是否为空
  5. */
  6. @Override
  7. public boolean isEmpty() {
  8. return start == end;
  9. }

这里就遇到了一个问题:怎么判定队列为空?!

  • 如果有1个元素,那么S = E - 1,因为S表示头元素当前位置,E表示为末元素位置+1;
  • 如果有2个元素,那么S = E - 2
  • 如果有3个元素,那么S = E - 3
  • ……
  • 如果有maxSize - 1个元素,那么考虑到回环的可能,S = E - (maxSize - 1),即S = (E+1)%maxSize;
  • 如果有maxSize个元素(满元素),那么S = E - maxSize,即S = E%maxSize;

如果你还没有明白问题在哪里,那我举个例子:假设这个队列的容量是5,经过一系列操作后,从位置3开始再次挨个儿插入元素。

你会发现,E已经把所有能遍历的位置都使用完了,而这里面没有适用于“查看队列是否为空”的情形!

咋办?……

那我们就必须使其中一个情形让出位置,来标记“队列为空”的情形,从逻辑上来说,只能放弃“有5个元素”的情况。(因为一旦可以检查是否有5各元素,就必定可以检查是否有4,3,2,1个元素的情形,那每个都不可以放弃)

思路改进

放弃了“有5个元素”的情况,那说明我们这个maxSize为5的数组就不能当做一个size为5的队列,那只能要求数组在实现循环队列的时候,队列的size等于数组size-1

判断队列是否为空的条件就可以写S是否等于E。

判断队列是否满了的条件就可以写S是否等于E+1。

代码改进

有了上述设计,代码只需要细微的调整。


  
  1. package foo.zongzhe.line.queue;
  2. /**
  3. * 用数组实现循环队列思路1:
  4. * start为头元素所在位置,end为末元素所在位置+1
  5. * 数组的size = 队列的size+1
  6. */
  7. public class ArrayQueueCircle1 extends ArrayQueue {
  8. /**
  9. * 创建队列构造器
  10. */
  11. public ArrayQueueCircle1(int queueSize) {
  12. super(queueSize);
  13. this.arraySize = queueSize + 1;
  14. array = new int[arraySize];
  15. start = 0; // 指向队列头部
  16. end = 0;
  17. arrayQueueDesc = "ArrayQueueCircle1";
  18. System.out.println(String.format( "%s initialized. start = %d, end = %d", arrayQueueDesc, start, end));
  19. }
  20. /**
  21. * 判断队列是否满
  22. */
  23. @Override
  24. public boolean isFull() {
  25. return start == (end + 1) % arraySize;
  26. }
  27. /**
  28. * 判断队列是否为空
  29. *
  30. * @return boolean 队列是否为空
  31. */
  32. @Override
  33. public boolean isEmpty() {
  34. return start == end;
  35. }
  36. /**
  37. * 添加数据到队列
  38. *
  39. * @param element 数据
  40. */
  41. @Override
  42. public void addElement(int element) {
  43. if (isFull()) {
  44. System.out.println(String.format( "%s is full, no values are allowed to add", arrayQueueDesc));
  45. } else {
  46. array[end] = element;
  47. end = (end + 1) % arraySize;
  48. System.out.println(String.format( "%s added one element %d. start = %d, end = %d", arrayQueueDesc, element, start, end));
  49. }
  50. }
  51. /**
  52. * 从队列中取出元素
  53. *
  54. * @return 元素的值
  55. */
  56. @Override
  57. public int takeElement() {
  58. if (isEmpty()) {
  59. throw new RuntimeException(String.format( "%s is empty, no values are able to retrieve", arrayQueueDesc));
  60. } else {
  61. int res = array[start];
  62. start = (start + 1) % arraySize;
  63. System.out.println(String.format( "%s took one element %d. start = %d, end = %d", arrayQueueDesc, res, start, end));
  64. return res;
  65. }
  66. }
  67. /**
  68. * 遍历队列
  69. */
  70. @Override
  71. public void showElements() {
  72. if (isEmpty()) {
  73. System.out.println(String.format( "%s is empty, no values are able to show", arrayQueueDesc));
  74. } else {
  75. System.out.print( "Iterating " + arrayQueueDesc + ": ");
  76. for ( int i = start; i != end; i = (i + 1) % arraySize) {
  77. System.out.print(array[i] + " ");
  78. }
  79. System.out.println();
  80. }
  81. }
  82. }

运行及输出

改进之后,运行ArrayQueueDemo就没有任何问题了,输出如下:


  
  1. > Task :ArrayQueueDemo.main()
  2. ArrayQueueCircle1 initialized. start = 0, end = 0
  3. ArrayQueueCircle1 isEmpty? true
  4. ArrayQueueCircle1 added one element 3. start = 0, end = 1
  5. Iterating ArrayQueueCircle1: 3
  6. ArrayQueueCircle1 isEmpty? false
  7. ArrayQueueCircle1 added one element 5. start = 0, end = 2
  8. ArrayQueueCircle1 added one element 2. start = 0, end = 3
  9. ArrayQueueCircle1 added one element 4. start = 0, end = 4
  10. ArrayQueueCircle1 added one element 5. start = 0, end = 5
  11. Iterating ArrayQueueCircle1: 3 5 2 4 5
  12. ArrayQueueCircle1 isFull? true
  13. ArrayQueueCircle1 took one element 3. start = 1, end = 5
  14. ArrayQueueCircle1 took one element 5. start = 2, end = 5
  15. ArrayQueueCircle1 added one element 3. start = 2, end = 0
  16. ArrayQueueCircle1 isFull? false
  17. Iterating ArrayQueueCircle1: 2 4 5 3

 

 

 

 

 

 

 

 

 

 

 

 

 

 


转载:https://blog.csdn.net/zongziczz/article/details/105921622
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场