第二十四篇 玩转数据结构——队列(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):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出.简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果. ... 
随机推荐
- 885-螺旋矩阵 - III
			885-螺旋矩阵 - III 在 R 行 C 列的矩阵上,我们从 (r0, c0) 面朝东面开始 这里,网格的西北角位于第一行第一列,网格的东南角位于最后一行最后一列. 现在,我们以顺时针按螺旋状行走 ... 
- Leetcode Week4 Find Minimum in Rotated Sorted Array II
			Question Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforeha ... 
- AspxDashboardView 更新参数
			AspxDashboardView 更新参数 function SetThrendDashboardView() { console.log("就是这样被你征服"); var to ... 
- WSO2 ESB XML定义语法(2)
			5.Proxy Service 配置 <proxy>元素用于定义Synapse代理服务. 通过基础Axis2引擎在指定的传输上创建和公开代理服务,根据标准的Axis2约定(即基于服务名称) ... 
- 对Ajax返回的json数据做处理报错
			这个错误出现的原因是我再返回数据为json时,我页面的Ajax没有指定dataType: 'json' 
- [Note]prufer
			[Note]Prufer编码 实现 不断删除度数为\(1\)的最小序号的点,并输出与其相连的节点的序号,直至树中只有两个节点. 性质 任何一棵\(n\)节点的树都可以唯一的用长度为\(n-2\)的pr ... 
- JS全选按钮练习
			<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/ ... 
- Map.getOrDefault被坑小记与optional
			错误使用样例 map.getOrDefault("account","").toString(); 在运行几小时后,发现报错空指针:查看源码如下: defaul ... 
- Jupyter Notebook快捷键总结
			1. Jupyter Notebook有两种mode Enter:进入edit模式 Esc:进入command模式 2. Command命令快捷键: A:在上方增加一个cell B:在下方增加一个ce ... 
- 到头来还是逃不开Java - Java13核心类
			Java13核心类 没有特殊说明,我的所有学习笔记都是从廖老师那里摘抄过来的,侵删 引言 兜兜转转到了大四,学过了C,C++,C#,Java,Python,学一门丢一门,到了最后还是要把Java捡起来 ... 
