<数据结构系列3>队列的实现与变形(循环队列)
数据结构第三课了,今天我们再介绍一种很常见的线性表——队列
就像它的名字,队列这种数据结构就如同生活中的排队一样,队首出队,队尾进队。以下一段是百度百科中对队列的解释:
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。
队列主要分为顺序队列以及循环队列,在这我两种队列都会实现一下, 并在最后进行一下性能上的测试比较。
ok,让我们开始今天的学习吧。
首先,还是先创建一个队列的接口:
public interface Queue<E> {
int getSize(); //得到队列元素个数
boolean isEmpty(); //判断队列是否为空
void enqueue(E e); //入队
E dequeue(); //出队
E getFront(); //查看队首元素
}
由于在<数据结构系列1>(https://www.cnblogs.com/LASloner/p/10721407.html)中我们已经实现了ArrayList,所以今天我们依旧就用Array来作为Queue的数据存储方式吧:
public class ArrayQueue<E> implements Queue<E> {
//这里用的是自己实现的Array,当然读者可以直接用Java自带的ArrayList,效果相同
private Array<E> array=new Array<>(); //有参构造
public ArrayQueue(int capacity) {
array=new Array<>(capacity);
}
//无参构造
public ArrayQueue() {
array=new Array<>();
} //获取队列元素个数
@Override
public int getSize() {
return array.getSize();
} //判断队列是否为空
@Override
public boolean isEmpty() {
return array.isEmpty();
} //获得队列的容量
public int getCapacity(){
return array.getCapacity();
} @Override
public void enqueue(E e) {
array.addLast(e);
} @Override
public E dequeue() {
return array.removeFirst();
} @Override
public E getFront() {
return array.get(0);
}
@Override
public String toString() {
StringBuilder res=new StringBuilder();
res.append("Queue: ");
res.append("front [");
for(int i = 0 ; i < array.getSize() ; i ++){
res.append(array.get(i));
if(i != array.getSize() - 1)
res.append(", ");
}
res.append("] rear"); return res.toString();
}
}
这并不是一件难事吧,那再让我们测试一下吧:
public static void main(String[] args) {
ArrayQueue<Integer> queue = new ArrayQueue<>();
for(int i = 0 ; i < 10 ; i ++){
queue.enqueue(i);
System.out.println("入队:"+queue);
if(i % 3 == 2){
queue.dequeue();
System.out.println("出队:"+queue);
}
}
}
输出如下:
入队:Queue: front [0] rear
入队:Queue: front [0, 1] rear
入队:Queue: front [0, 1, 2] rear
出队:Queue: front [1, 2] rear
入队:Queue: front [1, 2, 3] rear
入队:Queue: front [1, 2, 3, 4] rear
入队:Queue: front [1, 2, 3, 4, 5] rear
出队:Queue: front [2, 3, 4, 5] rear
入队:Queue: front [2, 3, 4, 5, 6] rear
入队:Queue: front [2, 3, 4, 5, 6, 7] rear
入队:Queue: front [2, 3, 4, 5, 6, 7, 8] rear
出队:Queue: front [3, 4, 5, 6, 7, 8] rear
入队:Queue: front [3, 4, 5, 6, 7, 8, 9] rear
这样就算是实现了顺序队列了,但是呢,由于顺序队列存储数据的方式使用了之前封装好的ArrayList,并不能很好的体现队列的这种数据结构,都没使用到front和rear,接下来,我们再实现一下循环队列,以此更好的了解队列。
那之前先看看什么是循环队列:

public class LoopQueue<E> implements Queue<E>{
private E[] data;
private int front;//队首
private int rear;//队尾
private int size; //有参构造
public LoopQueue(int capacity) {
data =(E[])new Object[capacity+1];//浪费一个空间用作判断 front==(rear+1)%(capacity+1)时为满
front=0;
rear=0;
size=0;
}
//无参构造
public LoopQueue() {
this(10);
} //返回队列中元素的个数
@Override
public int getSize() {
return size;
} //判断队列是否为空
@Override
public boolean isEmpty() {
return front==rear;
} //返回队列容量
public int getCapacity(){
return data.length - 1;
} //入队
@Override
public void enqueue(E e) {
if((rear+1)%data.length==front)//当队列满了之后扩容
resize(getCapacity()*2);
data[rear]=e;
rear=(rear+1)%data.length;//这里的data.length就代表Maxsize
size++;
} //出队
@Override
public E dequeue() {
if(isEmpty())
throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
E res=data[front];
data[front]=null;
front=(front+1)%data.length;
size--;
if(size==getCapacity()/4&&getCapacity()/2!=0)//动态缩容
resize(getCapacity()/2);
return res;
} //获取队首元素
@Override
public E getFront() {
if(isEmpty())
throw new IllegalArgumentException("Queue is empty.");
return data[front];
} //动态扩容
private void resize(int newCapacity) {
E[] newData=(E[])new Object[newCapacity+1];
for(int i=0;i<size;i++) {
newData[i]=data[(front+i)%data.length];
}
data=newData;
front=0;
rear=size;
} @Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append(String.format("Queue: size = %d , capacity = %d\t", size, getCapacity()));
res.append("front [");
for(int i = front ; i != rear ; i = (i + 1) % data.length){
res.append(data[i]);
if((i + 1) % data.length != rear)
res.append(", ");
}
res.append("] rear");
return res.toString();
} }
再写一个Main方法测试一下:
public static void main(String[] args) {
LoopQueue<Integer> queue = new LoopQueue<>();
for(int i = 0 ; i < 10 ; i ++){
queue.enqueue(i);
System.out.println("入队:"+queue); if(i % 3 == 2){
queue.dequeue();
System.out.println("出队:"+queue);
}
}
}
输出:
入队:Queue: size = 1 , capacity = 10 front [0] rear
入队:Queue: size = 2 , capacity = 10 front [0, 1] rear
入队:Queue: size = 3 , capacity = 10 front [0, 1, 2] rear
出队:Queue: size = 2 , capacity = 5 front [1, 2] rear
入队:Queue: size = 3 , capacity = 5 front [1, 2, 3] rear
入队:Queue: size = 4 , capacity = 5 front [1, 2, 3, 4] rear
入队:Queue: size = 5 , capacity = 5 front [1, 2, 3, 4, 5] rear
出队:Queue: size = 4 , capacity = 5 front [2, 3, 4, 5] rear
入队:Queue: size = 5 , capacity = 5 front [2, 3, 4, 5, 6] rear
入队:Queue: size = 6 , capacity = 10 front [2, 3, 4, 5, 6, 7] rear
入队:Queue: size = 7 , capacity = 10 front [2, 3, 4, 5, 6, 7, 8] rear
出队:Queue: size = 6 , capacity = 10 front [3, 4, 5, 6, 7, 8] rear
入队:Queue: size = 7 , capacity = 10 front [3, 4, 5, 6, 7, 8, 9] rear
那现在,循环队列相关的代码就全部完成了,需要注意的是,我们实现的循环队列是可以动态扩容的,实际上我实现的所有的线性表都是动态扩容的,希望读者注意。
既然实现了两种队列,就自然的做一下比较吧,首先做一下性能比较:
测试函数如下:
public class PerformanceTest {
private static double queueTest(Queue<Integer> q,int opCount) {
long startTime = System.nanoTime();//当前毫秒值 Random random = new Random();
for(int i = 0 ; i < opCount ; i ++)
q.enqueue(random.nextInt(Integer.MAX_VALUE));
for(int i = 0 ; i < opCount ; i ++)
q.dequeue(); long endTime = System.nanoTime(); return (endTime - startTime) / 1000000000.0;
} public static void main(String[] args) {
int opCount = 100000; ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
double time1 = queueTest(arrayQueue, opCount);
System.out.println("ArrayQueue, time: " + time1 + " s"); LoopQueue<Integer> loopQueue = new LoopQueue<>();
double time2 = queueTest(loopQueue, opCount);
System.out.println("LoopQueue, time: " + time2 + " s");
}
} 输出为:
ArrayQueue, time: 6.924613333 s
LoopQueue, time: 0.020527111 s
再看时间复杂度的比较:
ArrayQueue<E>
void enqueue(E) O(1)
E dequeue() O(n)
E front() O(1)
int getSize() O(1)
boolean isEmpty O(1) LoopQueue<E>
void enqueue(E) O(1)
E dequeue() O(1)
E front() O(1)
int getSize() O(1)
boolean isEmpty O(1)
经过对比不难看出,循环队列的效率要高于顺序队列,但是,也不得不说,循环队列在实现上要复杂一些。
哈哈,真是难得,竟然一天内更了两篇博文,继续加油!!
对于学习,四个字概括:至死方休
<数据结构系列3>队列的实现与变形(循环队列)的更多相关文章
- javascript实现数据结构与算法系列:队列 -- 链队列和循环队列实现及示例
1 队列的基本概念 队列(Queue):也是运算受限的线性表.是一种先进先出(First In First Out ,简称FIFO)的线性表.只允许在表的一端进行插入,而在另一端进行删除. 队首(fr ...
- [置顶] ※数据结构※→☆线性表结构(queue)☆============循环队列 顺序存储结构(queue circular sequence)(十)
循环队列 为充分利用向量空间,克服"假溢出"现象的方法是:将向量空间想象为一个首尾相接的圆环,并称这种向量为循环向量.存储在其中的队列称为循环队列(Circular Queue). ...
- C语言数据结构-循环队列的实现-初始化、销毁、清空、长度、队列头元素、插入、删除、显示操作
1.数据结构-循环队列的实现-C语言 #define MAXSIZE 100 //循环队列的存储结构 typedef struct { int* base; //基地址 int _front; //头 ...
- TypeScript算法与数据结构-队列和循环队列
本文涉及的源码,均在我的github.有两部分队列和循环队列.有问题的可以提个issue,看到后第一时间回复 1. 队列(Queue) 队列也是一种线性的数据结构, 队列是一种先进先出的数据结构.类似 ...
- 数据结构:循环队列(C语言实现)
生活中有非常多队列的影子,比方打饭排队,买火车票排队问题等,能够说与时间相关的问题,一般都会涉及到队列问题:从生活中,能够抽象出队列的概念,队列就是一个能够实现"先进先出"的存储结 ...
- 数据结构-循环队列(Python实现)
今天我们来到了循环队列这一节,之前的文章中,我介绍过了用python自带的列表来实现队列,这是最简单的实现方法. 但是,我们都知道,在列表中删除第一个元素和删除最后一个元素花费的时间代价是不一样的,删 ...
- 深入理解循环队列----循环数组实现ArrayDeque
我们知道队列这种数据结构的物理实现方式主要还是两种,一种是链队列(自定义节点类),另一种则是使用数组实现,两者各有优势.此处我们将要介绍的循环队列其实是队列的一种具体实现,由于一般的数组实现的队列结构 ...
- LeetCode 622——设计循环队列
1. 题目 设计你的循环队列实现. 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环.它也被称为"环形缓冲器". 循环队列 ...
- Java 循环队列的实现
队列概念 队列(Queue)是限定只能在一端插入.另一端删除的线性表.允许删除的一端叫做队头(front),允许插入的一端叫做队尾(rear),没有元素的队列称为“空队列”. 队列具有先进先出(FIF ...
- LeetCode 622:设计循环队列 Design Circular Queue
LeetCode 622:设计循环队列 Design Circular Queue 首先来看看队列这种数据结构: 队列:先入先出的数据结构 在 FIFO 数据结构中,将首先处理添加到队列中的第一个元素 ...
随机推荐
- CH5104 I-country[线性DP+分类讨论]
http://contest-hunter.org:83/contest/0x50%E3%80%8C%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E3%80%8D%E4%B ...
- 高大上的微信小程序中渲染html内容—技术分享
大部分Web应用的富文本内容都是以HTML字符串的形式存储的,通过HTML文档去展示HTML内容自然没有问题.但是,在微信小程序(下文简称为「小程序」)中,应当如何渲染这部分内容呢? 解决方案 wxP ...
- nginx设置负载均衡
...... why负载均衡,随着企业业务增长和海量请求,给服务端造成海量并发,导致响应不及时需扩容后端服务,前端需要负载均衡,均摊请求,让服务端吞吐请求的能力提升 单点服务方式,一个点挂掉整个服务就 ...
- BeanUtils对象属性copy的性能对比以及源码分析
1. 对象属性拷贝的常见方式及其性能 在日常编码中,经常会遇到DO.DTO对象之间的转换,如果对象本身的属性比较少的时候,那么我们采用硬编码手工setter也还ok,但如果对象的属性比较多的情况下,手 ...
- LOJ #6145. 「2017 山东三轮集训 Day7」Easy 点分树+线段树
这个就比较简单了~ Code: #include <cstdio> #include <algorithm> #define N 100004 #define inf 1000 ...
- git branch查看不到分支的名字解决办法
git branch查看不到分支的名字解决办法 <!-- 1. 先初始化 --> git init; <!-- 2. 接着创建瑶瑶的专属分支 --> git checkout ...
- [CSP-S模拟测试]:小W的魔术(数学 or 找规律)
题目传送门(内部题130) 输入格式 第一行一个整数$n$,表示字符串的长度. 第二行一个只包含小写字母的字符串$s$. 输出格式 一行一个整数表示答案对$998244353$取模后的结果. 样例 样 ...
- Zookeeper(二)数据模型
Zookeeper数据模型ZNode 问题 ZK的数据模型ZNodes是什么样的: 树形结构,每个叶子节点都存储着数据,且可挂载子节点: 路径作为叶子节点名称,数据作为叶子节点内的数据: Znode可 ...
- linux设置MySQL开机自动启动
step1: 通过chkconfig --list命令查看mysqld是否在列表中: step2: 如果列表中没有mysqld这个,需要先用这个命令添加:chkconfig --add mysqld ...
- LeetCode 337. 打家劫舍 III(House Robber III)
题目描述 小偷又发现一个新的可行窃的地点. 这个地区只有一个入口,称为“根”. 除了根部之外,每栋房子有且只有一个父房子. 一番侦察之后,聪明的小偷意识到“这个地方的所有房屋形成了一棵二叉树”. 如果 ...