美文网首页转载部分
数据结构与算法之美-队列

数据结构与算法之美-队列

作者: 沉江小鱼 | 来源:发表于2021-04-16 20:46 被阅读0次

    前言:本篇文章只是记录王争的数据结构与算法之美的学习笔记,写下来能强迫自己系统的再过一遍,加深理解。这门课以实际开发中遇到的问题为例,引入解决问题涉及到的的数据结构和算法,但不会讲的太细,最好结合一本实体书进行学习。

    CPU 资源是有限的,任务的处理速度和线程个数也不是线性正相关的,相反,过多的线程反而会导致CPU 频繁切换,处理性能下降。当我们向固定大小的线程池中请求一个线程时,如果请求非常多,线程池没有空闲资源,如何处理这个请求?就需要用到队列了。

    1. 队列

    队列很好理解,可以理解为排队买票,先来的买完票就走了,后来的在最后排队。

    跟栈不同,先进者先出,后进者后出,这就是典型的"队列",跟栈相同的是队列也是一种操作受限的线性表数据结构,栈只支持两个基本操作:入栈 Push出栈 Pop,队列也支持两个基本操作:入队 enqueue出队 dequeue,如下图所示:

    image.jpeg

    2. 队列的实现

    从队列的概念中,我们知道,队列和栈一样,都是抽象的数据结构,具有先进先出,后进后出的特性。

    我们有两种实现方式:

    • 用数组实现,则为顺序队列
    • 用链表实现,则为链式队列
    2.1 数组实现队列
    // 用数组实现的队列
    public class ArrayQueue {
      // 数组:items,数组大小:n
      private String[] items;
      private int n = 0;
      // head表示队头下标,tail表示队尾下标
      private int head = 0;
      private int tail = 0;
    
      // 申请一个大小为capacity的数组
      public ArrayQueue(int capacity) {
        items = new String[capacity];
        n = capacity;
      }
    
      // 入队
      public boolean enqueue(String item) {
        // 如果tail == n 表示队列已经满了
        if (tail == n) return false;
        items[tail] = item;
        ++tail;
        return true;
      }
    
      // 出队
      public String dequeue() {
        // 如果head == tail 表示队列为空
        if (head == tail) return null;
        // 为了让其他语言的同学看的更加明确,把--操作放到单独一行来写了
        String ret = items[head];
        ++head;
        return ret;
      }
    }
    

    队列的数组实现也挺简单,对于栈来说需要一个栈顶指针就行,因为栈的入栈和出栈都是在一个方向进行。队列则是不同的方向入队和出队,所以需要两个指针:head 指针,指向队头tail 指针,指向队尾

    如下图,当a b c d 依次入队之后,队列的 head 指针指向下标为 0 的位置,tail 指向下标为 4 的位置:


    image.png

    当我们调用两次出队操作之后,head 指向下标为 2 的位置,tail 仍然指向下标为 4 的位置:


    image.png

    如果用数组实现队列的话,会遇到一个问题:插入很多元素之后,队列的空间不够了。
    我们可以跟数组一样,用数据搬移,出队一个,就搬移一次,但是这样很耗费时间,想象一下,出队从下标为 0 的位置开始,相当于删除下标为 0 的数据,这样每出队一个数据,就需要搬移整个队列的数据,当然也可以稍微优化一下,就是当 tail 指针指向最后一个位置时,将所有数据都移到下标为 0 开始的位置上,整体进行搬移一次,代码如下:

    
       // 入队操作,将item放入队尾
      public boolean enqueue(String item) {
        // tail == n表示队列末尾没有空间了
        if (tail == n) {
          // tail ==n && head==0,表示整个队列都占满了
          if (head == 0) return false;
          // 数据搬移
          for (int i = head; i < tail; ++i) {
            items[i-head] = items[I];
          }
          // 搬移完之后重新更新head和tail
          tail -= head;
          head = 0;
        }
        
        items[tail] = item;
        ++tail;
        return true;
      }
    

    从代码中看到,当队列的 tail移动到数组最右边时,如果有新的数据入队,可以将 headtail 之间的数据,整体搬移到数组 0 到 tail-head的位置:

    image.png
    2.2 链表实现队列

    基于链表实现,head 指针指向链表的第一个节点tail 指向链表的最后一个节点
    当入队时,我们需要:

    tail->next = new_node;
    tail = tail->next;
    

    出队时:

    head = head->next
    
    image.png

    3. 循环队列

    顾名思义,就是一个首位相连的队列,在上面用数组实现的队列时,当 tail==n 时,需要对数据做搬移操作,这样性能会受到影响,我们可以让队列的首尾相连,当tail==n的时候,又开始从下标为 0 的地方入队,这就是循环队列,就是一个环了:

    image.png

    从上图中可以看到,队列大小为 8,目前 head = 4tail = 7,按照上面数组的实现,我们需要做数据搬移操作了。但是用循环队列的话,我们可以将新的数据 a 放到下标为 7 的位置,将新的数据 b 放到下标为 0 的位置,这样 tail 就指向下标为 1 的位置,如下图:

    `

    这样我们避免了数据搬移操作,但是判断队列为空,和队列满了的条件时就比较困难了:

    • 数组实现的非循环队列

      • 队空条件:tail == n
      • 队满条件:head == tail
    • 数组实现的循环队列

      • 队空条件:tail == n
      • 队满条件:(tail + 1) %n = head (需要自己画图理解一下)
    image.png
    像我图中画的队满的情况,tail=3,head=4,n=8,所以总结一下规律就是:(3+1)%8=4。多画几张队满的图,你就会发现,当队满时,(tail+1)%n=head

    并且当队列满时,tail 指向的位置其实是空的,所以循环队列会浪费一个数组的存储空间。

    4. 阻塞队列

    阻塞队列就是在队列为空的时候,从队头取数据会被阻塞,因为队列没数据啊,一直等到队列中有了数据才能返回;如果队列已经满了,那么插入数据的操作就会被阻塞,直到队列中有空闲位置后再插入数据,然后再返回:


    image.png

    其实上面的定义就是一个"生产者 - 消费者模型"。基于阻塞队列实现的"生产者 - 消费者模型",可以有效的协调生产和消费的速度。可以多配置几个“消费者”:


    image.png

    5. 对于线程资源的处理策略

    • 非阻塞处理:直接拒绝任务请求

    • 阻塞处理:将请求排队,使用队列方式存储,直到有空闲线程时,取出队首位置的任务执行

    • 基于链表的实现:
      可以实现一个支持无线排队的无界队列,但是可能会导致过多的请求无法处理,请求处理的时间较长,所以针对响应时间比较敏感的系统,基于链表实现的无线排队的线程池是不合适的

    • 基于数组的实现:
      首先为有界队列,即队列的大小有限,当排队请求超过队列大小时,可以直接拒绝这个请求,对于响应时间敏感的系统来说,相对合理

    6. 总结&练习

    队列可以用数组实现(顺序队列),可以用链表实现(链式队列),都是先进先出,在数组实现队列的时候,可以实现循环队列,这样就不用数据搬移了。

    • 数组实现队列
    • 链表实现队列
    • 实现循环队列

    相关文章

      网友评论

        本文标题:数据结构与算法之美-队列

        本文链接:https://www.haomeiwen.com/subject/awfylltx.html