定义
队列和栈一样,是一种操作受限的线性表。队列只允许在队头进行删除操作,叫做出队dequeue
,在队尾进行插入操作,叫做入队enqueue
。是一种先进先出(First In First Out
)的线性表,简称为FIFO
。
基本操作
- 入队列
enqueue
,在队列末尾插入一个元素。 - 出队列
dequeue
,删除队头元素。
实现
和栈一样,队列可以使用数组和链表实现。具体实现方法是使用head
和tail
两个指针分别指向队头和队尾。用数组实现的队列叫做顺序队列,用链表实现的队列叫做链式队列。
顺序队列
- 对于有
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)
。 - 循环队列事先申请好空间,使用期间不释放,存在存储元素个数和空间浪费的问题;链式队列每次申请和释放结点存在一些时间开销。
- 总结:在可以确定队列长度最大值的情况下,建议使用循环队列;无法预估队列长度时使用链式队列。
队列的应用
- 阻塞队列
- 引入阻塞操作,从空队列的队头删除数据会被阻塞,从满队列的队尾插入数据会被阻塞。
- 并发队列
- 线程安全的队列。
- 排队请求
- 当线程池没有空闲线程,有新的任务请求线程资源时,一般有两种处理策略。第一种是非阻塞的处理方式,直接拒绝任务请求;另一种是阻塞的处理方式,将请求排队,等到有空闲线程时,取出排队的请求继续处理。
- 按照先进先出的原则,我们使用队列存储排队请求。
- 基于链表实现的队列,可以实现一个支持无限排队的无界队列(
unbounded queue
),但是可能会导致过多的请求排队等待,请求处理的响应时间过长。不适合用于对响应时间比较敏感的系统。 - 基于数组实现的有界队列(
bounded queue
),队列大小有限,当线程池中排队的请求超过队列大小时,接下来的请求就会被拒绝。不过对于队列大小的设置非常讲究。
栈与队列的比较
- 栈(
stack
):仅在表尾进行插入和删除操作的线性表。- 顺序栈:使用一维数组实现顺序存储,将下标为
0
的一端作为栈底,定义一个top
变量来标记栈顶元素在数组中的位置。 - 链式栈:将单链表的头指针
head
和栈的栈顶指针top
合二为一,去掉单链表中常用的头结点。
- 顺序栈:使用一维数组实现顺序存储,将下标为
- 队列(
queue
):仅在表尾进行插入操作,在表头进行删除操作的线性表。- 顺序队列:使用一维数组实现顺序存储,将下标为0的一端作为队头。
- 循环队列:引入
head
和tail
两个指针,使得顺序队列头尾相接,解决了出队操作涉及的数据搬移。
- 循环队列:引入
- 链式队列:使用队头指针
head
指向链队列的头结点,队尾指针tail
指向链队列的尾结点。
- 顺序队列:使用一维数组实现顺序存储,将下标为0的一端作为队头。