超神之路 数据结构 2 —— Queue队列实现和循环队列和普通队列的性能比较
创始人
2024-02-02 06:52:45

        接上一篇继续往下挖,在上一篇,我们实现了一个属于自己的动态数组。利用这个动态数组,我们来实现一个基于动态数组,一个属于自己的普通队列Queue。

        Queue 是一种它许我们从表的一段进行删除,表的另一端进行插入的线性表。可能也有人问啥叫线性表。

线性表:线性表是n个具有相同特性的数据元素的有限序列 (n>=0)。
n是线性表的长度,n=0就是空表,n>0时,表通常记作(a1,a2,a3...a_n)
除了a1外,表中每个元素 a[i] 均有唯一的前驱 a[i-1];
除了an外,表中每个元素 a[i] 均有唯一的后继 a[i+1];
线性表在逻辑上是线性结构,

        认识完了Queue,我们再来认识下Java util包下,Doug Lea 实现的Queue接口各个方法的定义。然后我们尽可能向JDK的源码靠近。

package java.util;public interface Queue extends Collection {//添加一个元素,成功时返回true, 没有空间则报一个非法状态异常。boolean add(E e);//也时添加元素,比add更友好,add只给你异常,不给你false,offer给你返false。boolean offer(E e);//检索并删除此队列的头。此方法与poll的不同之处在于,它在队列为空时抛出异常。E remove();//检索并删除此队列的头,如果此队列为空则返回null。E poll();//检索但不删除此队列的头。此方法与peek的不同之处在于,它在队列为空时抛出异常。E element();//检索但不删除此队列的头,如果此队列为空则返回null。E peek();
}

        全部翻译了一遍,相信看出来了,add,offer都是添加元素,remove和poll也都是删除首元素,element和peek也都是查看首元素。唯一的区别是,前者给你异常,想停掉代码流;而后者给你null,让你自己处理。

        add,remove,element ===>均异常,offer,poll,peek ===> 均给null。

        这个接口还继承了 Collection 接口,Collection自然会对多种底层实现的集合容器实现一个统一的迭代器,迭代器这块1.8之后又加了一些实用defaut方法,这些就不关注了。主要关注下面2个方法:

 size方法,返回容器中元素个数,isEmpty方法判断是否为空。

        把以上抛异常的三个方法 add,remove,element 做个整合,我们推出以下接口,尽量来靠近JDK。

package com.company.queue;/*** 队列** @author wang da wei* 2021/11/7 0:24* @version 1.0.0*/
public interface Queue {/*** 入队* @param e*/void add(E e);/*** 出队* @return 出队的元素*/E remove();/*** 查看队首的元素。* @return 队首元素。*/E element();/*** 是否是空的。* @return 是否为空。*/boolean isEmpty();/*** 查看线性表中元素个数。* @return 元素个数*/int size();
}

有了这种接口,我们就可以做2种实现了。先做一个普通的队列,再做一个循环队列。

基于上一篇的动态数组,我们很容易的就可以实现出一个FIFO的队列。代码如下:

package com.company.queue.common;import com.company.array.DynamicArray;
import com.company.queue.Queue;public class ArrayQueue implements Queue {private DynamicArray dynamicArray ;public ArrayQueue() {this.dynamicArray = new DynamicArray<>();}@Overridepublic void add(E e) {//添加到尾部 即 arr[size]位置。dynamicArray.add(e,dynamicArray.getSize());}@Overridepublic E remove() {//移除头部元素,并返回。return dynamicArray.remove(0);}@Overridepublic E element() {//查看第一个元素。return dynamicArray.get(0);}@Overridepublic boolean isEmpty() {//查看是否为空return dynamicArray.getSize() == 0;}@Overridepublic int size() {//获取动态数组中实际元素的个数。return dynamicArray.getSize();}}

再来一个循环队列的实现:

package com.company.queue.loop;import com.company.queue.Queue;public class LoopQueue implements Queue {private E[] data;private int front, tail;private int size;  // 思考:不用 size 变量,如何实现?//如果用户知道最多为循环队列传递多少元素的时候,可以直接传入一个capacity.public LoopQueue(int capacity) {data = (E[]) new Object[capacity + 1];front =0;tail = 0;size = 0 ;}public LoopQueue(){this(10);}public int getCapacity(){return data.length -1;}@Overridepublic void add(E e) {if( (tail +1 )% data.length  == front ){resize(getCapacity() * 2);}data[tail] =e;tail = (tail + 1) % data.length;size ++;}private void resize(int newCapacity) {E[] newData = (E[]) new Object[newCapacity +1];for(int i = 0 ;i< size ;i++){int old_index = (i + front) % data.length;newData[i] = data[old_index];}data = newData;front = 0;tail =size;}@Overridepublic E remove() {if(isEmpty() ) throw new IllegalArgumentException("cannot dequeue from an empty queue .");E ret = data[front];data[front] = null;front = (front + 1) % data.length;size--;return ret;}@Overridepublic E element() {if(isEmpty() ) throw new IllegalArgumentException("cannot dequeue from an empty queue .");return data[front];}@Overridepublic boolean isEmpty() {return front == tail;}@Overridepublic int size() {return size;}@Overridepublic String toString() {StringBuilder sb = new StringBuilder(String.format("Queue : size/capacity = %d/%d \nfront [ ",size,getCapacity()));for(int i = 0;i != tail; i = (i+1)% data.length){sb.append(data[i]);//当前索引不是最后一个元素。if((i+1)%data.length != tail ){sb.append(" , ");}}sb.append(" ] tail ");return sb.toString();}public static void main(String[] args) {LoopQueue queue  = new LoopQueue<>();for(int i = 0 ;i<20;i++){queue.add(i);System.out.println(queue);if(i%3 == 2){queue.remove();System.out.println(queue);}}}
}

两种队列的性能比较:

package com.company.queue;import com.company.queue.common.ArrayQueue;
import com.company.queue.loop.LoopQueue;import java.util.Random;public class MainQueueTest {public static void main(String[] args) {int opCount = 10_0000;ArrayQueue arrayQueue = new ArrayQueue<>();double arrayQueueTime = testQueue(arrayQueue, opCount);System.out.println(arrayQueueTime);LoopQueue loopQueue = new LoopQueue<>();double loopQueueTime = testQueue(loopQueue, opCount);System.out.println(loopQueueTime);}public static double testQueue(Queue queue,int opCount){long start = System.nanoTime();Random random = new Random();for(int i = 0 ;i

        由于循环队列出队操作是O(1)的时间复杂度,所以当数据量越大的时候,一般有更好的性能优势。

相关内容

热门资讯

应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...