【数据结构】之队列

悠悠 2022-01-30 10:17 355阅读 0赞

笔者日常:
感谢孤尽(花名)大侠今天给我解答线程安全的问题,在这里推荐孤大侠书籍《码出高效》,带你深入基础,走进阿里规范!


目录

队列基本知识

队列的常见实现方式

  1. 数组实现
  2. 链表实现

队列的常见分类

  1. 数组队列:基于数组的原理进行实现。
  2. 链表队列:基于链表的原理进行实现。

Java对上述队列的代码实现(示例)

  1. 对(数组)顺序队列的(简单)实现
  2. 对(数组)循环队列的(简单)实现
  3. 对链表队列的(简单)实现

队列基本知识:

  1. 队列是一种特殊的操作受限制的线性表,它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,**进行插入操作的端称为队尾,进行删除操作的端称为队头**。队列的插入操作称为入队,删除操作称为出队。队列就像一列进入隧道的火车,隧道就是队列,火车车厢就是元素,进入隧道就是从隧道的这头(队尾)插入元素;出隧道就是从隧道的另一头(队头)删除元素。队列具有**先进先出(FIFOfirst in first out)**的特点。

20190519022245198.png


队列的常见实现方式:

  1. 队列的实现方式较多,我们常见的主要有两种,分别是数组实现与链表实现。基于数组的队列效率较高,但有长度限制(当然可以在程序内部以新建数组的方式来变相达到动态扩容的效果)。基于链表的队列,要动态创建和删除节点,效率较低,但是可以动态增长。

数组实现:

20190519022336224.gif

链表实现:

20190519022400211.gif


队列的常见分类:

  1. 分类标准不同,队列分类也不一样。按照入队出队时是否阻塞可分为阻塞队列、非阻塞队列。按照实现方式来分的话,队列可分为数组队列、链表队列等。其中,数组队列按照空间是否可以重复利用来分的话,又可分为顺序队列和循环队列。

注:对于数组队列而言,循环队列要比顺序队列实用得多。

数组队列:基于数组的原理进行实现。

相关概念之溢出(图示说明):

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2p1c3RyeV9kZW5n_size_16_color_FFFFFF_t_70

顺序队列(主要以图示说明):从队头删除元素,从队尾增加元素。被删除的元素空出来的位置,不会被后来增加的
元素重复利用。

第一种:队头不动,出队列时队头后的所有元素向前移动。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2p1c3RyeV9kZW5n_size_16_color_FFFFFF_t_70 1

注:一旦有元素出队,那么所有元素都得移动,开销较大。

第二种:队头移动,出队列时队头向后移动一个位置。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2p1c3RyeV9kZW5n_size_16_color_FFFFFF_t_70 2

注:此方式可能出现假溢出。

循环队列(主要以图示说明):从队头删除元素,从队尾增加元素。被删除的元素空出来的位置,会被后来增加的元素重复利用。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2p1c3RyeV9kZW5n_size_16_color_FFFFFF_t_70 3

链表队列:基于链表的原理进行实现。

2019051902290663.png

注:可结合上文中的链表实现gif动图进行理解。


Java对上述队列的代码实现(示例):

声明**:**这里只是简单的实现,如果想要了解更全面实现方式的话,推荐阅读各种阻塞非阻塞队列的源码。

先给出一个下面三个队列实现中都会用到的异常类:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2p1c3RyeV9kZW5n_size_16_color_FFFFFF_t_70 4

对(数组)顺序队列的(简单)实现:

  1. import java.io.Serializable;
  2. import java.util.Arrays;
  3. /**
  4. * (简单实现)顺序队列
  5. *
  6. * @author JustryDeng
  7. * @date 2019/5/18 21:19
  8. */
  9. public class SequenceQueue<E> implements Serializable{
  10. private static final long serialVersionUID = -807097163996913371L;
  11. /** 元素容器 */
  12. private final Object[] items;
  13. /** 队列的容量(即:数组的长度) */
  14. private int capacity;
  15. /** 队头 */
  16. private int front;
  17. /** 队尾 */
  18. private int rear;
  19. /**
  20. * 构造器
  21. *
  22. * @param capacity
  23. * 队列的容量
  24. * @date 2019/5/18 21:55
  25. */
  26. public SequenceQueue(int capacity) {
  27. this.capacity = capacity;
  28. items = new Object[capacity];
  29. front = 0;
  30. rear = 0;
  31. }
  32. /**
  33. * 入队
  34. *
  35. * @param e
  36. * 入队的元素
  37. * @date 2019/5/18 22:27
  38. */
  39. public void offer(E e) {
  40. if (e == null) {
  41. throw new IllegalArgumentException("arg must be not null!");
  42. }
  43. if (rear >= capacity) {
  44. throw new MyQueueException("the queue already full!");
  45. }
  46. items[rear] = e;
  47. rear++;
  48. }
  49. /**
  50. * 出队
  51. *
  52. * @return 出队的那个元素
  53. * @date 2019/5/18 22:25
  54. */
  55. public E poll() {
  56. if (isEmpty()) {
  57. throw new MyQueueException("the queue is empty!");
  58. }
  59. // 保留队列的front端的元素的值
  60. E oldValue = itemAt(front);
  61. // 释放队列的front端的元素
  62. items[front] = null;
  63. front++;
  64. return oldValue;
  65. }
  66. /**
  67. * 返回队列头元素,但不会将该元素出列
  68. *
  69. * @return 队列头元素
  70. * @date 2019/5/18 22:07
  71. */
  72. public E peek() {
  73. if (isEmpty()) {
  74. throw new MyQueueException("the queue is empty!");
  75. }
  76. return itemAt(front);
  77. }
  78. /**
  79. * 返回指定位置处的元素
  80. *
  81. * @param index
  82. * 索引位置
  83. *
  84. * @return 返回index位置处的元素
  85. * @date 2019/5/18 22:21
  86. */
  87. @SuppressWarnings("unchecked")
  88. public E itemAt(int index) {
  89. return (E) items[index];
  90. }
  91. /**
  92. * 判断队列是否为空队列
  93. *
  94. * @return 是/否
  95. * @date 2019/5/18 22:04
  96. */
  97. public boolean isEmpty() {
  98. return rear == front;
  99. }
  100. /**
  101. * 判断队列是否已满
  102. *
  103. * @return 是/否
  104. * @date 2019/5/18 22:04
  105. */
  106. public boolean isFull() {
  107. return rear >= capacity;
  108. }
  109. /**
  110. * 获取队列的实际有效大小(即:获取队列中元素的个数)
  111. *
  112. * @return 获取当前队列的大小
  113. * @date 2019/5/18 21:54
  114. */
  115. public int size() {
  116. return rear - front;
  117. }
  118. /**
  119. * 清空队列
  120. *
  121. * @date 2019/5/18 22:04
  122. */
  123. public void clear() {
  124. // 将数组所有元素赋为null
  125. Arrays.fill(items, null);
  126. // 首尾指针重置
  127. front = 0;
  128. rear = 0;
  129. }
  130. @Override
  131. public String toString() {
  132. if (isEmpty()) {
  133. return "[]";
  134. } else {
  135. StringBuilder sb = new StringBuilder("[");
  136. for (int i = front; i < rear; i++) {
  137. sb.append(items[i].toString())
  138. .append( ", ");
  139. }
  140. int len = sb.length();
  141. return sb.delete(len - 2, len).append("]").toString();
  142. }
  143. }
  144. /**
  145. * 测试一下
  146. *
  147. * @author JustryDeng
  148. * @date 2019/5/19 1:14
  149. */
  150. public static void main(String[] args) {
  151. SequenceQueue<String> queue = new SequenceQueue<>(5);
  152. // 入队
  153. queue.offer("A");
  154. queue.offer("B");
  155. queue.offer("C");
  156. // 出队(出队后,元素就不在队列中了)
  157. System.out.println(queue.poll());
  158. System.out.println(queue.poll());
  159. // 队列是否为空
  160. System.out.println(queue.isEmpty());
  161. // 队列中元素个数
  162. System.out.println(queue.size());
  163. // 获取队列中的头元素(此操作后,该元素仍然在队列中)
  164. System.out.println(queue.peek());
  165. // 入队
  166. queue.offer("D");
  167. queue.offer("E");
  168. // 队列是否已满
  169. System.out.println(queue.isFull());
  170. // 验证queue的toString函数
  171. System.out.println(queue);
  172. // 清空队列
  173. queue.clear();
  174. queue.offer("F");
  175. // 验证queue的toString函数
  176. System.out.println(queue);
  177. // 队列中元素个数
  178. System.out.println(queue.size());
  179. }
  180. }

注:这里以实现第二种顺序队列为例。

对(数组)循环队列的(简单)实现:

  1. import java.io.Serializable;
  2. import java.util.Arrays;
  3. /**
  4. * (简单实现)循环队列
  5. *
  6. * @author JustryDeng
  7. * @date 2019/5/18 18:58
  8. */
  9. public class CircleQueue<E> implements Serializable {
  10. /** 元素容器 */
  11. private final Object[] items;
  12. /** 队列的容量(即:数组的长度) */
  13. private int capacity;
  14. /** 队头 */
  15. private int front;
  16. /** 队尾 */
  17. private int rear;
  18. /**
  19. * 构造器
  20. *
  21. * @param capacity
  22. * 队列的容量
  23. * @date 2019/5/18 21:55
  24. */
  25. public CircleQueue(int capacity) {
  26. this.capacity = capacity;
  27. items = new Object[capacity];
  28. front = 0;
  29. rear = 0;
  30. }
  31. /**
  32. * 入队
  33. *
  34. * @param e
  35. * 入队的元素
  36. * @date 2019/5/18 22:27
  37. */
  38. public void offer(E e) {
  39. if (e == null) {
  40. throw new IllegalArgumentException("arg must be not null!");
  41. }
  42. if (rear - front >= capacity) {
  43. throw new MyQueueException("the queue already full!");
  44. }
  45. int currIndex = rear % capacity;
  46. items[currIndex] = e;
  47. rear++;
  48. }
  49. /**
  50. * 出队
  51. *
  52. * @return 出队的那个元素
  53. * @date 2019/5/18 22:25
  54. */
  55. public E poll() {
  56. if (isEmpty()) {
  57. throw new MyQueueException("the queue is empty!");
  58. }
  59. int currIndex = front % capacity;
  60. E oldValue = itemAt(currIndex);
  61. items[currIndex] = null;
  62. front++;
  63. return oldValue;
  64. }
  65. /**
  66. * 返回队列头元素,但不会将该元素出列
  67. *
  68. * @return 队列头元素
  69. * @date 2019/5/18 22:07
  70. */
  71. public E peek() {
  72. if (isEmpty()) {
  73. throw new MyQueueException("the queue is empty!");
  74. }
  75. return itemAt(front % capacity);
  76. }
  77. /**
  78. * 返回指定位置处的元素
  79. *
  80. * @param index
  81. * 索引位置
  82. * @return 返回index位置处的元素
  83. * @date 2019/5/18 22:21
  84. */
  85. @SuppressWarnings("unchecked")
  86. public E itemAt(int index) {
  87. return (E) items[index];
  88. }
  89. /**
  90. * 判断队列是否为空队列
  91. *
  92. * @return 是/否
  93. * @date 2019/5/18 22:04
  94. */
  95. public boolean isEmpty() {
  96. return rear == front;
  97. }
  98. /**
  99. * 判断队列是否已满
  100. *
  101. * @return 是/否
  102. * @date 2019/5/18 22:04
  103. */
  104. public boolean isFull() {
  105. return rear - front >= capacity;
  106. }
  107. /**
  108. * 获取队列的实际有效大小(即:获取队列中元素的个数)
  109. *
  110. * @return 获取当前队列的大小
  111. * @date 2019/5/18 21:54
  112. */
  113. public int size() {
  114. return rear - front;
  115. }
  116. /**
  117. * 清空队列
  118. *
  119. * @date 2019/5/18 22:04
  120. */
  121. public void clear() {
  122. // 将数组所有元素赋为null
  123. Arrays.fill(items, null);
  124. // 首尾指针重置
  125. front = 0;
  126. rear = 0;
  127. }
  128. @Override
  129. public String toString() {
  130. if (isEmpty()) {
  131. return "[]";
  132. } else {
  133. StringBuilder sb = new StringBuilder("[");
  134. int curIndex;
  135. for (int i = front; i < rear; i++) {
  136. curIndex = i % capacity;
  137. sb.append(items[curIndex].toString())
  138. .append(", ");
  139. }
  140. int len = sb.length();
  141. return sb.delete(len - 2, len).append("]").toString();
  142. }
  143. }
  144. /**
  145. * 测试一下
  146. *
  147. * @author JustryDeng
  148. * @date 2019/5/19 1:14
  149. */
  150. public static void main(String[] args) {
  151. CircleQueue<String> queue = new CircleQueue<>(5);
  152. // 入队
  153. queue.offer("A");
  154. queue.offer("B");
  155. queue.offer("C");
  156. queue.offer("D");
  157. // 出队(出队后,元素就不在队列中了)
  158. System.out.println(queue.poll());
  159. System.out.println(queue.poll());
  160. // 队列是否为空
  161. System.out.println(queue.isEmpty());
  162. // 队列中元素个数
  163. System.out.println(queue.size());
  164. // 获取队列中的头元素(此操作后,该元素仍然在队列中)
  165. System.out.println(queue.peek());
  166. // 入队
  167. queue.offer("E");
  168. queue.offer("F");
  169. queue.offer("G");
  170. // 验证queue的toString函数
  171. System.out.println(queue);
  172. // 队列是否已满
  173. System.out.println(queue.isFull());
  174. // 清空队列
  175. queue.clear();
  176. // 验证queue的toString函数
  177. System.out.println(queue);
  178. // 队列是否已满
  179. System.out.println(queue.isFull());
  180. }
  181. }

对链表队列的(简单)实现:

  1. import java.io.Serializable;
  2. /**
  3. * (简单实现)链式(链表)队列
  4. *
  5. * @author JustryDeng
  6. * @date 2019/5/19 1:32
  7. */
  8. public class LinkQueue<E> implements Serializable{
  9. private static final long serialVersionUID = 3748038449750184916L;
  10. /**
  11. * 定义一个内部类Node,Node实例代表链队列的节点
  12. *
  13. * @author JustryDeng
  14. * @date 2019/5/19 1:31
  15. */
  16. private class Node {
  17. /** 此节点的数据 */
  18. private E data;
  19. /** 指向下个节点的引用 */
  20. private Node next;
  21. /** 全参构造 */
  22. private Node(E data, Node next) {
  23. this.data = data;
  24. this.next = next;
  25. }
  26. }
  27. /** 头节点 */
  28. private Node front;
  29. /** 尾节点 */
  30. private Node rear;
  31. /** 此链式队列中已包含的节点数 */
  32. private int size;
  33. /**
  34. * 空链队列 构造
  35. */
  36. public LinkQueue() {
  37. front = null;
  38. rear = null;
  39. size = 0;
  40. }
  41. /**
  42. * 入队
  43. *
  44. * @param e
  45. * 入队的元素
  46. * @date 2019/5/19 1:36
  47. */
  48. public void offer(E e) {
  49. // 如果该链队列还是空链队列
  50. if (front == null) {
  51. if (e == null) {
  52. throw new IllegalArgumentException("arg must be not null!");
  53. }
  54. front = new Node(e, null);
  55. // 只有一个节点,front、rear都指向该节点
  56. rear = front;
  57. } else {
  58. // 创建新节点
  59. Node newNode = new Node(e, null);
  60. // 让当前尾节点的next指向新增的节点
  61. rear.next = newNode;
  62. // 更新尾节点引用,以新节点作为新的尾节点
  63. rear = newNode;
  64. }
  65. size++;
  66. }
  67. /**
  68. * 出队
  69. *
  70. * @return 出队的那个元素
  71. * @date 2019/5/18 22:25
  72. */
  73. public E poll() {
  74. if (isEmpty()) {
  75. throw new MyQueueException("the queue is empty!");
  76. }
  77. Node oldFront = front;
  78. front = front.next;
  79. oldFront.next = null;
  80. size--;
  81. return oldFront.data;
  82. }
  83. /**
  84. * 返回队列头元素,但不会将该元素出列
  85. *
  86. * @return 队列头元素
  87. * @date 2019/5/18 22:07
  88. */
  89. public E peek() {
  90. if (isEmpty()) {
  91. throw new MyQueueException("the queue is empty!");
  92. }
  93. return front.data;
  94. }
  95. /**
  96. * 判断队列是否为空队列
  97. *
  98. * @return 是/否
  99. * @date 2019/5/18 22:04
  100. */
  101. public boolean isEmpty() {
  102. return size == 0;
  103. }
  104. /**
  105. * 获取此链式队列中已包含的节点数
  106. *
  107. * @return 此链式队列中已包含的节点数
  108. * @date 2019/5/19 1:36
  109. */
  110. public int size() {
  111. return size;
  112. }
  113. /**
  114. * 清空队列
  115. *
  116. * @date 2019/5/18 22:04
  117. */
  118. public void clear() {
  119. front = null;
  120. rear = null;
  121. size = 0;
  122. }
  123. @Override
  124. public String toString() {
  125. // 链队列为空链队列时
  126. if (isEmpty()) {
  127. return "[]";
  128. } else {
  129. StringBuilder sb = new StringBuilder("[");
  130. for (Node current = front; current != null; current = current.next) {
  131. sb.append(current.data.toString())
  132. .append(", ");
  133. }
  134. int len = sb.length();
  135. return sb.delete(len - 2, len).append("]").toString();
  136. }
  137. }
  138. /**
  139. * 测试一下
  140. *
  141. * @author JustryDeng
  142. * @date 2019/5/19 1:14
  143. */
  144. public static void main(String[] args) {
  145. LinkQueue<String> queue = new LinkQueue<>();
  146. // 入队
  147. queue.offer("A");
  148. queue.offer("B");
  149. queue.offer("C");
  150. // 出队(出队后,元素就不在队列中了)
  151. System.out.println(queue.poll());
  152. System.out.println(queue.poll());
  153. // 队列是否为空
  154. queue.offer("D");
  155. queue.offer("E");
  156. System.out.println(queue.isEmpty());
  157. // 队列中元素个数
  158. System.out.println(queue.size());
  159. // 获取队列中的头元素(此操作后,该元素仍然在队列中)
  160. System.out.println(queue.peek());
  161. // 入队
  162. queue.offer("F");
  163. queue.offer("G");
  164. // 验证queue的toString函数
  165. System.out.println(queue);
  166. // 清空队列
  167. queue.clear();
  168. // 验证queue的toString函数
  169. System.out.println(queue);
  170. // 队列中元素个数
  171. System.out.println(queue.size());
  172. }
  173. }

#

^_^ 如有不当之处,欢迎指正

^_^ 参考链接 https://baike.baidu.com/item/%E9%…7/14580481?fr=aladdin https://blog.csdn.net/zhang21722668/article/details/82155301 https://www.jb51.net/article/130858.htm

^_^ 学习视频 51CTO学院《数据结构与算法》,讲师张晨光

^_^ 本文已被收录进《程序员成长笔记(五)》,笔者JustryDeng

发表评论

表情:
评论列表 (有 0 条评论,355人围观)

还没有评论,来说两句吧...

相关阅读

    相关 数据结构队列

    概述 在java5中新增加了[Java][].util.Queue接口,用以支持队列的常见操作。Queue接口与List、Set同一级别,都是继承了Collection接

    相关 数据结构队列

    一、 队列的顺序存储结构及实现 1.0 啥叫队列 栈就相当往杯子里放东西,而队列就相当于往管子里放水,只能这头进,那头出 队列就是只允许在一段进行插入操作,另一端

    相关 数据结构队列

    概念 可以把队列想象成排队买票,先来的人先买,后来的人站在队尾排队后买,不允许插队,先进者先出,这就是典型的“队列”。 特点 先进者先出,后进者后出,不允许在中间

    相关 数据结构队列

    数据结构队列的相关学习: 简介 队列是是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。 队列是一种先进先出的线性表,简称FIFO允许插入的以端称为队尾,

    相关 数据结构队列

    笔者日常:         感谢孤尽(花名)大侠今天给我解答线程安全的问题,在这里推荐孤大侠书籍《码出高效》,带你深入基础,走进阿里规范! ---------------