[第五章] 队列

注:此专栏内容主要参考极客时间-数据结构与算法之美

1. 概念

  • 先进者先出,这就是典型的“队列”;
  • 入队 enqueue(),放一个数据到队列尾部,出队 dequeue(),从队列头部取一个元素;
  • 用数组实现的队列叫作顺序队列,用链表实现的队列叫作链式队列;
  • 基于链表的实现方式,可以实现一个支持无限排队的无界队列(unbounded queue),但是可能会导致过多的请求排队等待,请求处理的响应时间过长。所以,针对响应时间比较敏感的系统,基于链表实现的无限排队的线程池是不合适的。
  • 基于数组实现的有界队列(bounded queue),队列的大小有限,所以线程池中排队的请求超过队列大小时,接下来的请求就会被拒绝,这种方式对响应时间敏感的系统来说,就相对更加合理。不过,设置一个合理的队列大小,也是非常有讲究的。

在这里插入图片描述


2.数组实现队列

  • 需要两个指针:一个是 head 指针,指向队头、一个是 tail 指针,指向队尾。
    在这里插入图片描述
  • 入队一个元素,tail往后移一位。出队一个元素,head往后移动一位;
  • 随着出队操作,head 和 tail 都会持续往后移动。当tail 移动到最右边,即使数组中还有空闲空间,也无法继续往队列中添加数据了。
  • 当tail移动到数组的最右边后。这时,如果有新的数据入队,我们可以将 head 到 tail 之间的数据,整体搬移到数组中 0 到 tail-head 的位置。
    在这里插入图片描述

3. 环形队列

  • 循环队列,顾名思义,它长得像一个环。原本数组是有头有尾的,是一条直线。现在我们把首尾相连,扳成了一个环。
    在这里插入图片描述
  • 依旧是两个指针head、tail的实现思路
    队列为空的判断条件:head==tail;
    队列满了的判断条件: (tail+1)%n=head;
  • 当队列满时,图中的 tail 指向的位置实际上是没有存储数据的。所以,循
    环队列会浪费一个数组的存储空间。 在这里插入图片描述

4. 阻塞队列

  • 阻塞队列其实就是在队列基础上增加了阻塞操作;
  • 当队列为空的时候,从队头取数据会被阻塞。因为此时还没有数据可取,直到队列中有了数据才能返回;
  • 如果队列已经满了,那么插入数据的操作就会被阻塞,直到队列中有空闲位置后再插入数据,然后再返回;
    在这里插入图片描述

5. 并发队列

  • 线程安全的队列我们叫作并发队列。最简单直接的实现方式是直接在 enqueue()、
    dequeue() 方法上加锁,但是锁粒度大并发度会比较低,同一时刻仅允许一个存或者取操作。
  • 实际上,基于数组的循环队列,利用 CAS 原子操作,可以实现非常高效的并发队列。
    这也是循环队列比链式队列应用更加广泛的原因。

6. 递归

  • 写递归代码的关键就是找到如何将大问题分解为小问题的规律,并且基于此写
    出递推公式,然后再推敲终止条件,最后将递推公式和终止条件翻译成代码。
  • 只要遇到递归,我们就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤。
  • 函数调用会使用栈来保存临时变量。每调用一个函数,都会将临时变量封装为栈帧压入内存栈,等函数执行完成返回时,才出栈。系统栈或者虚拟机栈空间一般都不大。如果递归求解的数据规模很大,调用层次很深,一直压入栈,就会有堆栈溢出的风险。我们可以通过在代码中限制递归调用的最大深度的方式来解决这个问题;
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值