【算法】数据结构:队列

tech2024-04-22  51

今天来介绍队列!

打算边学习,边把数据结构和一些常用的算法整理下,文章大概都会按照,是什么,为什么,怎么样来写,不算科普文章,更多是自己的一些思考和想法,还有找到一些优质资源的分享!

一、队列是什么?

队列和栈一样,都是受限制的数据结构,记住一点:FIFO(first input first output),先进先出,就和排队一样(火车站买票,前面的人先买完离开)

所以队列提供的api,应该类似

enQueue/add/offer:往队列中添加数据的方法

deQueue/remove/poll:从队列头把数据移除

peek:不移除数据,但是可以查看队列头的数据

大家可以看看,java的Queue接口是如何定义的!

java Queue中 add/offer,element/peek,remove/poll区别(作者:行者小朱):https://blog.csdn.net/u012050154/article/details/60572567

 

二、为什么用队列?

其实我觉得为什么用队列也非常好理解,现实生活中,遇到排队的问题不要太多!各种买票,点餐,任何有顺序的事情,并且一次性处理不完,自然就要排队。

举个代码中的例子:

客户发起了创建自定义人群的任务,数据库一次性只能处理3个(线程池中只有3个线程),那么用户提交了10个怎么办,不就是创建一个队列,把所有任务按照先来后到的顺序放入到队列中,3个线程空闲,就直接到队列中拿头部的任务,其他的任务等待就好了,等执行的线程有空了,就会再次到队列中拿出排前面的任务去执行

所以如果java事先帮你实现了队列,那么你调用起来就非常的方便,新来一条数据,直接塞入队列,只需要每次都从队列里拿数据出来处理就可以了,不需要管队列里具体是什么(当然如果要写出性能好的代码,还是要研究下具体实现的嘿嘿~)

 

三、队列底层是什么?

队列底层可以使用数组和链表来实现

1、使用链表实现

(1)就是拿两个指针(front和rear),一个指向头节点,一个指向尾节点

(2)、插入数据,就往尾部插入,rear向后移动

(3)、移除数据,就是front往后移动一位,头节点没有被引用就会被GC自动回收掉

2、使用数组实现

(1)、由于数组是固定长度的,所以数组实现的队列,就会有初始长度,而且当队列满了的时候,就要自动扩容(参照ArrayList这种底层是数组的集合的扩容实现)

(2)、还是要拿两个指针(front和rear),一个指向头节点,一个指向尾节点

(3)、但是问题来了,头节点的数据被移除,咋办,有两种选择,其一是每次移除,就把所有数据迭代一遍(往前移一位);其二,实现环形数组(至于环形数组咋搞,大家百度吧,就搜索环形数组实现队列,或者直接去看LeetCode上面的题目)

环形数组实现队列的难点就在于,如何判断队列空和满的情况!要么通过一个变量来记录现有的element个数,要么就需要多一个空间,以此用%运算来区分空和满(如果不知道我在说什么的,还是建议大家百度相关文章)

 

四、队列有哪些可拓展的功能

我不知道我能不能说全了,说一些常用的我见过的。

 

1、双向队列

双向队列,可以看看java中的Deque接口的具体实现类,可以作为队列也可以作为栈来使用,还是挺厉害的!!

下图是我白嫖来的

Java双向队列Deque栈与队列(作者:森林屿麓):https://blog.csdn.net/u013967628/article/details/85210036

看最下面两个类,区别就是一个用链表实现,一个用数组实现!

 

2、优先级队列

有时候,我们不想按照数据进入的顺序来取数据,我们想定义优先级(这不就像是现实社会,你排队的时候,突然来了个VIP用户,然后他很自然就排到你前面去了,哭了TAT),比如一个优先级很高的任务,他将会被先处理!

大家可以看看这个类,PriorityQueue,他其实用数组实现了二叉树的结构,来完成排序的,大家有兴趣可以自行百度一些文章,也可以看我下面分享的这篇(但是可能需要你先理解二叉树这种数据结构,堆排序这一方面的知识)

JAVA中PRIORITYQUEUE详解(作者:Elliott_Su):https://www.cnblogs.com/Elliott-Su-Faith-change-our-life/p/7472265.html

 

3、适用于并发场景下的队列(阻塞和线程安全)

我想把这两点放在一起讲

(1)、先说阻塞

如果你有去看queue的一些实现,你会发现,有些的名称上面有写blocking

阻塞队列使用于什么场景?

消息队列!(时至今日,消息队列应该也挺常见了吧,比如kafka,各种xxxMQ)

一个线程从队列中取数据的时候,如果队列为空,那么线程就会阻塞,等到队列有新的数据进来,线程才会取到数据,继续后续的操作,相对的,如果队列已满,线程插入数据的时候也会阻塞,等到有空间了再继续处理

 

(2)、线程安全

所以说到消息队列,一般消费者都会有多个吧,这就是多线程的情况,那我们就要考虑线程安全的问题!

可以看到基本所有的BlockingQueue都来自于java.util.concurrent(JUC)这个多线程包,你可以点到这些类里面具体看看,你会惊人的发现,他们基本上都使用了ReentrantLock这个类

这里就不具体介绍ReentrantLock类了,大家可以自行百度,这其实就是一个上锁的操作(synchronized关键字也是用来加锁的)

所以,阻塞队列使用了锁,那么他们就可以用到正式开发中的多线程环境中!!

 

五、相关题目

emmm我发现LeetCode上有标签

1、访问:https://leetcode-cn.com/problemset/all/

2、右下角找到标签分类

3、进去做题就好了

我觉得:

首先,要能够自己实现环形队列

其次,知道队列底层是什么,能够发现工作中什么样的场景使用哪个(java已经定义好的)类性能更好(总不能啥轮子都自己造吧,写完还一堆bug就很糟糕)

 

好,本次的分享就到这里,下次再见咯,我想我可能也要整理下数组和链表这两个数据结构,再说吧,反正慢慢来,也让自己能够更系统的了解知识,有任何问题,或者批评,麻烦大家给我留言~

最新回复(0)