数据结构与算法4:操作受限的线性表——队列queue

文章目录

    • 定义
    • 基本操作
    • 实现
      • 顺序队列
        • 循环队列
      • 链式队列
      • 循环队列与链式队列的比较
    • 队列的应用
    • 栈与队列的比较

定义

队列和栈一样,是一种操作受限的线性表。队列只允许在队头进行删除操作,叫做出队dequeue,在队尾进行插入操作,叫做入队enqueue。是一种先进先出(First In First Out)的线性表,简称为FIFO

基本操作

  • 入队列enqueue,在队列末尾插入一个元素。
  • 出队列dequeue,删除队头元素。

实现

和栈一样,队列可以使用数组和链表实现。具体实现方法是使用headtail两个指针分别指向队头和队尾。用数组实现的队列叫做顺序队列,用链表实现的队列叫做链式队列

顺序队列

  • 对于有n个元素的队列,我们使用长度大于n的数组进行顺序存储,把队列的所有元素存储在数组的前n个单元,数组下标为0的一端即为队头。
  • 入队列操作:在队尾追加一个元素,时间复杂度O(1)
  • 出队列操作:弹出数组下标为0位置的元素,其余元素向前移动,时间复杂度为O(n)
class ArrayQueueUnderFlow(ValueError):
    """队列位置定位错误"""
    pass


class ArrayQueue:
    """顺序队列,使用python的内建类型list列表实现"""
    def __init__(self, maxsize):
        """初始化"""
        self.items = [maxsize]  # 队列元素
        self.head = 0  # 队头指针
        self.tail = 0  # 队尾指针
        self.maxsize = maxsize  # 队列长度

    def get_length(self):
        """顺序队列元素个数"""
        return self.tail - self.head

    def is_empty(self):
        """队列是否为空"""
        return self.head == self.tail

    def is_full(self):
        """队列是否为满"""
        return self.get_length == self.maxsize

    def enqueue(self, item):
        """元素入队列"""
        if self.is_full():
            raise SqQueueUnderFlow('of enqueue')
        self.items[self.tail] = item
        self.tail += 1

    def dequeue(self):
        """元素出队列"""
        if self.is_empty():
            raise SqQueueUnderFlow('of dequeue')
        e = self.items[self.head]
        self.items[self.head] = None
        self.head += 1
        return e
循环队列
  • 为了避免顺序队列在队头进行出队操作时频繁的数据搬移,引入循环队列。头尾相接的顺序队列称为循环队列。
  • 空循环队列:head == tail
  • 满循环队列:(tail + 1) % n == head,其中n为存储队列的数组长度
  • 队列长度计算公式:(tail - head + n) % n,取值范围为[0, n)
  • 入队列:若队列已满入队失败,否则将尾指针赋值为当前元素,尾指针后移tail = (tail + 1) % n,其中n为存储队列的数组长度,时间复杂度O(1)
  • 出队列:若队列为空出队列失败,否则返回头指针对应的元素,头指针后移head = (head + 1) % n,其中n为存储队列的数组长度,时间复杂度O(1)
class ArrayQueueUnderFlow(ValueError):
    """队列位置定位错误"""
    pass



class CircularQueue:
    """循环队列类"""
    def __init__(self, maxsize):
        """初始化"""
        self.items = [maxsize]  # 队列元素
        self.head = 0  # 队头指针
        self.tail = 0  # 队尾指针
        self.maxsize = maxsize  # 队列长度

    def get_length(self):
        """顺序队列元素个数"""
        return (self.tail - self.head + self.maxsize) % self.maxsize

    def is_empty(self):
        """队列是否为空"""
        return self.head == self.tail

    def is_full(self):
        """队列是否为满"""
        return (self.tail + 1) % self.maxsize == self.head

    def enqueue(self, item):
        """元素入队列"""
        if self.is_full():
            raise ArrayQueueUnderFlow('of enqueue')
        self.items[self.tail] = item
        self.tail = (self.tail + 1)%self.maxsize

    def dequeue(self):
        """元素出队列"""
        if self.is_empty():
            raise ArrayQueueUnderFlow('of dequeue')
        e = self.items[self.head]
        self.items[self.head] = None
        self.head = (self.head + 1) % self.maxsize
        return e

链式队列

  • 队列的链式存储结构,就是线性表的单链表,限制只能尾部插入头部删除,简称为链式队列。
  • 使用队头指针head指向链队列的头结点,队尾指针tail指向链队列的尾结点。
  • 入队列操作:在单链表尾部插入一个值等于入队列元素的结点,修改尾指针,时间复杂度O(1)
  • 出队列操作:删除单链表的头部结点,修改头指针,时间复杂度O(1)
class SinglyLinkedNode:
    """单链表结点类"""
    def __init__(self, value):
        self.value = value


class LinkedQueueUnderFlow(ValueError):
    """链表位置定位错误"""
    pass


class LinkedQueue:
    """链式队列"""
    def __init__(self):
        """初始化"""
        self.head = None  # 队头指针
        self.tail = None  # 队尾指针
        self.num = 0  # 队列元素个数

    def get_length(self):
        """返回队列元素个数"""
        return self.num

    def is_empty(self):
        """判断是否为空队列"""
        return self.head == self.tail

    def enqueue(self, item):
        """元素入队列"""
        s = SinglyLinkedNode(item)
        self.tail.next = s
        self.tail = s
        self.num += 1

    def dequeue(self):
        """元素出队列"""
        if self.is_empty():
            raise LinkedQueueUnderFlow('in dequeue')
        e = self.head.next.value
        self.head = self.head.next
        self.num -= 1
        return e

循环队列与链式队列的比较

  • 两者的基本操作时间复杂度都为O(1)
  • 循环队列事先申请好空间,使用期间不释放,存在存储元素个数和空间浪费的问题;链式队列每次申请和释放结点存在一些时间开销。
  • 总结:在可以确定队列长度最大值的情况下,建议使用循环队列;无法预估队列长度时使用链式队列。

队列的应用

  • 阻塞队列
    • 引入阻塞操作,从空队列的队头删除数据会被阻塞,从满队列的队尾插入数据会被阻塞。
  • 并发队列
    • 线程安全的队列。
  • 排队请求
  1. 当线程池没有空闲线程,有新的任务请求线程资源时,一般有两种处理策略。第一种是非阻塞的处理方式,直接拒绝任务请求;另一种是阻塞的处理方式,将请求排队,等到有空闲线程时,取出排队的请求继续处理。
  2. 按照先进先出的原则,我们使用队列存储排队请求。
  3. 基于链表实现的队列,可以实现一个支持无限排队的无界队列(unbounded queue),但是可能会导致过多的请求排队等待,请求处理的响应时间过长。不适合用于对响应时间比较敏感的系统。
  4. 基于数组实现的有界队列(bounded queue),队列大小有限,当线程池中排队的请求超过队列大小时,接下来的请求就会被拒绝。不过对于队列大小的设置非常讲究。

栈与队列的比较

  • 栈(stack):仅在表尾进行插入和删除操作的线性表。
    • 顺序栈:使用一维数组实现顺序存储,将下标为0的一端作为栈底,定义一个top变量来标记栈顶元素在数组中的位置。
    • 链式栈:将单链表的头指针head和栈的栈顶指针top合二为一,去掉单链表中常用的头结点。
  • 队列(queue):仅在表尾进行插入操作,在表头进行删除操作的线性表。
    • 顺序队列:使用一维数组实现顺序存储,将下标为0的一端作为队头。
      • 循环队列:引入headtail两个指针,使得顺序队列头尾相接,解决了出队操作涉及的数据搬移。
    • 链式队列:使用队头指针head指向链队列的头结点,队尾指针tail指向链队列的尾结点。

你可能感兴趣的:(数据结构与算法,数据结构与算法,队列,线性表,数据结构,Python实现队列)