循环队列结构分析
ArrayQueue假溢出
我们在利用数组实现队列的时候,发现数组队列会出现假溢出问题,即队列还没有满,但不能再往队列中放入元素了,如下图所示:
在数据进行出队的时候,每一个元素出队,指向队列头元素的head就会向后移动,导致head之前的元素被“遗忘”了,无法再次利用,出队的代码如下:
@Override public E poll() { if (!isEmpty()) { E value = (E) elements[head]; // 移除头部元素 elements[head] = null; head++; return value; } return null; }
当然,我们可以对数组队列进行一些优化。在插入元素的时候,我们检查一下tail是否已经指向了队尾,如果指向了队尾并且head不等于0的情况下,说明发生了假溢出,需要进行元素迁移工作,将head和tail之间的元素整体移动到 0 到 tail - head
的位置,这样就可以避免假溢出问题了(还是上面的图),实现代码如下:
/** * 由于数组队列存在假溢出问题,所谓要进行数据搬运 */ @Override public boolean add(E e) { Objects.requireNonNull(e); if (tail == capacity) { if (head == 0) { // 证明队列是满的 throw new IllegalStateException("Queue full"); } // 如果head 不等于0,证明head之前的空间是空着的,所以需要进行数据搬运 for (int i = head; i < tail; i++) { elements[i - head] = elements[i]; } // 搬运完更新head 和 tail head = 0; tail -= head; // tail = tail - head } // 正常操作 elements[tail++] = e; return true; }
循环队列
上面我们知道了假溢出问题,并且找到了解决方式,但在添加元素的时候可能会出现数据移动工作,并不是十分优雅,有没有一种比较好的方式去处理这个问题呢?既不出现假溢出问题,又不移动数据,还...(想的太多),答案是有的。仔细想想如果我们将数组队列收尾相连会出现什么情况,哈哈,根本不用管假溢出问题了,添加元素的时候直接往后移动tail(tail 加 1)就完事了,是不是很酷?大致结构如下图所示:
空和满队列判断
看完描述和图示,我们可能会有疑问,head 和 tail的位置一直是不固定的,那么该如何判断队列是否满的呢?
当循环队列为空时,很明显head 和 tail 是相等的,如下图所示:
那么如何判断队列是否满了呢? 由于我们用head与tail相等来判断队列为空的情况,所以队列满时,必须有一个空位来由我们的tail指向,如下图所示:
上图只是一种情况,总结来说,队头指针head在队尾指针tail的下一位置时,队满。由于是环形结构,所以需要进行求余运算,例如(5 + 1) % 8 = 3
,总结规律下来,就是
(head + 1) % capacity = tail
由于这种机制的存在,当队列满时,总有一个位置是被浪费掉的。
构造函数和成员变量
循环队列默认把元素存到数组里,所以这里用数组来保存队列里的元素,在构造函数中初始化容量和数组,代码如下:
// 队列内部数组默认容量 private static final int DEFAULT_CAPACITY = 8; // 队列内部数组的容量 private int capacity; // 保存元素的数组 private Object[] elements; // 指向队列头部 private int head; // 指向队列尾部 private int tail; /** * 默认构造函数初始化 */ public CircularQueue() { capacity = DEFAULT_CAPACITY; elements = new Object[capacity]; } /** * 指定队列内部数组容量进行初始化 * @param capacity 指定容量 */ public CircularQueue(int capacity) { this.capacity = capacity; elements = new Object[capacity]; }
下面就不废话了,直接看实现吧,通过上面的分析,代码没什么难度了。嘻嘻_
入队
@Override public boolean add(E e) { Objects.requireNonNull(e); // 判断队列是否满了 if ((tail + 1) % capacity == head) { throw new IllegalStateException("Queue full"); } elements[tail] = e; tail = (tail + 1) % capacity; return true; }
出队
@Override public E poll() { // 如果 head == tail, 队列就为null if (isEmpty()) { return null; } E value = (E) elements[head]; head = (head + 1) % capacity; return value; }
打印
/** * 打印 */ public String toString() { if (isEmpty()) { return "[]"; } else { StringBuilder sb = new StringBuilder("["); for (int i = head; i != tail; i = (i + 1) % capacity) { sb.append(elements[i].toString() + ", "); } int len = sb.length(); return sb.delete(len - 2, len).append("]").toString(); } }
源代码在这里
title: 循环队列结构分析
tags: [数据结构, 队列]
author: Mingshan
categories: [数据结构, 队列]
date: 2018-11-01
本文来自博客园,作者:mingshan,转载请注明原文链接:https://www.cnblogs.com/mingshan/p/17793514.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库