第二十四篇 玩转数据结构——队列(Queue)
- 队列也是一种线性结构;
- 相比数组,队列所对应的操作数是队列的子集;
- 队列只允许从一端(队尾)添加元素,从另一端(队首)取出元素;
- 队列的形象化描述如下图:

- 队列是一种先进先出(First In First Out)的数据结构;
- 任务目标如下:
Queue<E>
·void enqueue(E) //入队
·E dequeue() //出队
·E getFront() //查看队首元素
·int getSize() //查看队列中元素的个数
·boolean isEmpty() //查看队列是否为空
- 需要提一下,从用户的角度来看,只要实现上述操作就好,具体底层实现,用户并不关心,实际上,底层确实有多种实现方式。
- 我们准备在之前实现的动态数组基础上,来实现"队列"这种数据结构。
- 先定义一个接口Interface,如下:
public interface Queue<E> {
int getSize(); boolean isEmpty(); void enqueue(E e); E dequeue(); E getFront(); }- 实现基于Array类的ArrayQueue类,并进行测试:
public class ArrayQueue<E> implements Queue<E> {
private Array<E> array; //构造函数
public ArrayQueue(int capacity) {
array = new Array<>(capacity);
} //无参数构造函数
public ArrayQueue() {
array = new Array<>();
} //实现getSize()方法
@Override
public int getSize() {
return array.getSize();
} //实现isEmpty方法
@Override
public boolean isEmpty() {
return array.isEmpty();
} //实现getCapacity方法
public int getCapacity() {
return array.getCapacity();
} //实现enqueue方法
@Override
public void enqueue(E e) {
array.addLast(e);
} //实现dequeue方法
@Override
public E dequeue() {
return array.removeFirst();
} //实现getFront方法
@Override
public E getFront() {
return array.getFirst();
} //方便打印测试
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append("Queue: ");
res.append("front [");
for (int i = ; i < array.getSize(); i++) {
res.append(array.get(i));
if (i != array.getSize() - ) {
res.append(", ");
}
}
res.append("] tail");
return res.toString();
} // 测试
public static void main(String[] args){
ArrayQueue<Integer> queue = new ArrayQueue<>(); // 测试入队
for(int i=;i<;i++){
queue.enqueue(i);
}
System.out.println(queue); // 测试出队
queue.dequeue();
System.out.println(queue);
}
}- 输出结果:
Queue: front [, , , , ] tail
Queue: front [, , , ] tail
3.. 数组队列的时间复杂度分析:
ArrayQueue<E>
·void enqueue(E) O() 均摊
·E dequeue() O(n)
·E getFront() O()
·int getSize() O()
·boolean isEmpty() O()
- 数组队列的出队操作的复杂度是O(n),性能很差,解决方法就是使用循环队列(Loop Queue)
- 循环队列的示意图如下:


- 实现循环队列的业务逻辑,并进行测试:
public class LoopQueue<E> implements Queue<E> { private E[] data;
private int front, tail;
private int size; //构造函数
public LoopQueue(int capacity) {
data = (E[]) new Object[capacity + ];
front = ;
tail = ;
size = ;
} //无参数构造函数
public LoopQueue() {
this(); //直接调用有参数的构造函数,然后传入一个默认值
} //实现getCapacity方法
public int getCapacity() {
return data.length - ;
} //实现isEmpty方法
@Override
public boolean isEmpty() {
return front == tail;
} //实现getSize方法
@Override
public int getSize() {
return size;
} //实现enqueue方法
@Override
public void enqueue(E e) {
//判断队列是否已满
if ((tail + ) % data.length == front) {
resize(getCapacity() * );
} data[tail] = e;
tail = (tail + ) % data.length;
size++;
} //实现dequeue方法
@Override
public E dequeue() {
//判断队列是否为空
if (isEmpty()) {
throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
} E ret = data[front];
data[front] = null;
front = (front + ) % data.length;
size--; if (size == getCapacity() / && getCapacity() / != ) {
resize(getCapacity() / );
}
return ret;
} //实现getFront方法
@Override
public E getFront() {
if (isEmpty()) {
throw new IllegalArgumentException("Queue is empty.");
}
return data[front];
} //实现resize方法
private void resize(int newCapacity) {
E[] newData = (E[]) new Object[newCapacity + ];
for (int i = ; i < size; i++) {
newData[i] = data[(i + front) % data.length];
}
data = newData;
front = ;
tail = size;
} //方便打印测试
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append(String.format("Queue: size=%d, capacity=%d\n", size, getCapacity()));
res.append("front [");
for (int i = front; i != tail; i = (i + ) % data.length) {
res.append(data[i]);
if ((i + ) % data.length != tail) {
res.append(", ");
}
}
res.append("] tail");
return res.toString();
} //测试
public static void main(String[] args) {
LoopQueue<Integer> queue = new LoopQueue<>(); // 测试入队
for (int i = ; i < ; i++) {
queue.enqueue(i);
}
System.out.println(queue); // 测试出队
queue.dequeue();
System.out.println(queue);
}
}- 输出结果:
Queue: size=, capacity=
front [, , , , ] tail
Queue: size=, capacity=
front [, , , ] tail
5.. 循环队列的复杂度分析
LoopQueue<E>
·void enqueue(E) O() 均摊
·E dequeue() O() 均摊
·E getFront() O()
·int getSize() O()
·boolean isEmpty() O()
6.. 使用简单算例测试ArrayQueue与LoopQueue的性能差异
import java.util.Random; public class Main { // 测试使用q运行opCount个enqueue和dequeue操作所需要的时间,单位:秒
private static double testQueue(Queue<Integer> q, int opCount) {
long startTime = System.nanoTime(); Random random = new Random();
for (int i = ; i < opCount; i++) {
q.enqueue(random.nextInt(Integer.MAX_VALUE));
}
for (int i = ; i < opCount; i++) {
q.dequeue();
} long endTime = System.nanoTime();
return (endTime - startTime) / 1000000000.0;
} public static void main(String[] args) { int opCount = ; ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
double time1 = testQueue(arrayQueue, opCount);
System.out.println("ArrayQueue, time: " + time1 + " s"); LoopQueue<Integer> loopQueue = new LoopQueue<>();
double time2 = testQueue(loopQueue, opCount);
System.out.println("LoopQueue, time: " + time2 + " s"); }
}- 输出结果
ArrayQueue, time: 2.88077896 s
LoopQueue, time: 0.01140229 s
第二十四篇 玩转数据结构——队列(Queue)的更多相关文章
- 第二十八篇 玩转数据结构——堆(Heap)和有优先队列(Priority Queue)
1.. 优先队列(Priority Queue) 优先队列与普通队列的区别:普通队列遵循先进先出的原则:优先队列的出队顺序与入队顺序无关,与优先级相关. 优先队列可以使用队列的接口,只是在 ...
- 第二十六篇 玩转数据结构——二分搜索树(Binary Search Tree)
1.. 二叉树 跟链表一样,二叉树也是一种动态数据结构,即,不需要在创建时指定大小. 跟链表不同的是,二叉树中的每个节点,除了要存放元素e,它还有两个指向其它节点的引用,分别用Node l ...
- 第二十五篇 玩转数据结构——链表(Linked List)
1.. 链表的重要性 我们之前实现的动态数组.栈.队列,底层都是依托静态数组,靠resize来解决固定容量的问题,而"链表"则是一种真正的动态数据结构,不需要处理固定容 ...
- 第三十四篇 玩转数据结构——哈希表(HashTable)
1.. 整型哈希函数的设计 小范围正整数直接使用 小范围负整数整体进行偏移 大整数,通常做法是"模一个素数" 2.. 浮点型哈希函数的设计 转成整型进行处理 3.. 字符串 ...
- 第二十九篇 玩转数据结构——线段树(Segment Tree)
1.. 线段树引入 线段树也称为区间树 为什么要使用线段树:对于某些问题,我们只关心区间(线段) 经典的线段树问题:区间染色,有一面长度为n的墙,每次选择一段墙进行染色(染色允许覆盖),问 ...
- SpringBoot第二十四篇:应用监控之Admin
作者:追梦1819 原文:https://www.cnblogs.com/yanfei1819/p/11457867.html 版权声明:本文为博主原创文章,转载请附上博文链接! 引言 前一章(S ...
- Android UI开发第二十四篇——Action Bar
Action bar是一个标识应用程序和用户位置的窗口功能,并且给用户提供操作和导航模式.在大多数的情况下,当你需要突出展现用户行为或全局导航的activity中使用action bar,因为acti ...
- 【转】Android UI开发第二十四篇——Action Bar
Action bar是一个标识应用程序和用户位置的窗口功能,并且给用户提供操作和导航模式.在大多数的情况下,当你需要突出展现用户行为或全局导航的activity中使用action bar,因为acti ...
- Python之路【第二十四篇】Python算法排序一
什么是算法 1.什么是算法 算法(algorithm):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出.简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果. ...
随机推荐
- 事件分析法学习笔记(ESM)
事件分析法基础学习笔记 1.定义 事件分析法是研究某事件的发生对组织价值的影响以及影响程度. 或者说研究特定事件对组织行为的影响. notes:事件分析法的关键点在于探讨所关注事件在某个时段产生的 ...
- 查看Sql Server库中某张表的结构
--快速查看表结构(比较全面的) SELECT CASE WHEN col.colorder = THEN obj.name ELSE '' END AS 表名, col.colorder AS 序号 ...
- 如何通过给MM修电脑培养感情
文章来自网络 在修之前,向MM反复声明,这电脑故障是有硬件和软件之分的,如果是硬件故障,例如显卡风扇不转了,显示器连线老化,显示器分辨率超出显示器指标,等等都会导致黑屏啊,这个我不回家用专门的工具是修 ...
- Wannafly Camp 2020 Day 1C 染色图 - 组合数学,整除分块
定义一张无向图 G=⟨V,E⟩ 是 k 可染色的当且仅当存在函数 f:V↦{1,2,⋯,k} 满足对于 G 中的任何一条边 (u,v),都有 f(u)≠f(v). 定义函数 g(n,k) 的值为所有包 ...
- [SDOI2008] 校门外的区间 - 线段树
U T 即将区间 \(T\) 范围赋值为 \(1\) I T 即将区间 \(U - T\) 范围赋值为 \(0\) D T 即将区间 \(T\) 赋值为 \(0\) C T 由于 \(S=T-S=T( ...
- .NET知识梳理——4.特性Attribute
1. 特性 1.1 特性Attribute 特性就是一个类,继承自Attribute抽象类(该类无抽象方法.避免实例化),约定俗成用Attribute类结尾,标记时可省略掉Attribu ...
- itext操作表单域导出PDF,俗称扣模板
/** * templateUrl 模板文件路径,包含文件名 * targetUrl 目标路径 * dateMap 填充数据 */public class CreatePdfUtil { public ...
- 动态规划 ---- 最长公共子序列(Longest Common Subsequence, LCS)
分析: 完整代码: // 最长公共子序列 #include <stdio.h> #include <algorithm> using namespace std; ; char ...
- K3/Cloud点按钮打开单据,列表,动态表单,简单账表和直接Sql报表示例
BOS IDE中配置了个界面,拖了动态表单界面,加了5个测试按钮. 点击“打开单据”维护界面, 会跳转到一个新的主界面页签,[物料]新增 点击“打开列表”,会弹出[物料]列表界面 点击“打开动态表单” ...
- 【Docker入门篇】
" @[toc] 所谓Docker Docker最初是dotCloud公司创始人Solomon Hykes在法国期间发起的一个公司内部项目,于2013年3月以Apache2.0授权协议开源, ...