数据结构: 队列 讲解
一、概念:
先进者先出,这就是 队列。
把它想象成排队买票,先来的先买,后来的人只能站末尾,不允许插队。
队列跟栈非常相似,支持的操作也很有限,最基本的操作也是两个:
- 入队 enqueue(),放一个数据到队列尾部;
- 出队 dequeue(),从队列头部取一个元素;
所以,队列跟栈一样,也是一种操作受限的线性表数据结构
。
二、顺序队列和链式队列:
跟栈一样,队列可以用数组来实现,也可以用链表来实现。
- 用数组实现的队列叫作顺序队列;
- 用链表实现的队列叫作链式队列;
1.顺序队列:
队列,需要两个指针,一个head 指针,指向队头;一个tail 指针,指向队尾
。
class SqQueue(object):
"""顺序队列"""
def __init__(self, size):
"""
size:指定队列的大小
"""
self.data = list(None for _ in range(size + 1))
self.max_size = size + 1
self.head = 0
self.tail = 0
self.length = 0
def get_length(self):
"""获取队列的长度"""
return self.length
def is_full(self):
"""判断队列是否满了"""
if (self.tail + 1) % self.max_size == self.head:
return True
return False
def is_empty(self):
"""判断队列是否为空"""
if self.head == self.tail:
return True
return False
def en_queue(self, element):
"""进入队列, 从队尾插入"""
if self.is_full():
raise IndexError('Queue is full!')
else:
self.data[self.tail] = element
self.tail = (self.tail + 1) % self.max_size
self.length += 1
def de_queue(self):
"""出队列, 从对头取出"""
if self.is_empty():
raise IndexError('Queue is empty!')
else:
del_element = self.data[self.head]
self.data[self.head] = None
self.head = (self.head + 1) % self.max_size
self.length -= 1
return del_element
def show_queue(self):
"""显示队列元素, 从队首开始显示"""
if self.is_empty():
raise IndexError('Queue is empty!')
else:
j = self.head
while j != self.tail:
print(self.data[j])
j = (j + 1) % self.max_size
queue = SqQueue(5)
for i in range(5):
queue.en_queue(i)
queue.show_queue()
print('----------')
queue.de_queue()
queue.show_queue()
如图所示:
-
当a、b、c、d依次入队之后,队列中的 head 指针,指向下标为 0 的位置,tail 指针指向下标为 4 的位置。
-
当调用两次出队操作后,队列中 head 指针指向下标为 2 的位置, tail 指针仍然指向下标为 4 的位置。
随着不停的进行入队、出队操作,head 和 tail 都会持续往后移动。当tail 移动最右边,即使数组中还有空间,也无法继续往队列中添加数据了。
-
如果使用数据迁移,每次进、出队都相当于删除数组下标为 0 的数据,要搬移整个队列中的数据,这样出队操作的
时间复杂度从原来的O(1) 变为了 O(n)
。扫描二维码关注公众号,回复: 8489345 查看本文章 -
优化思路,在出队时,不用搬移数据,如果没有空闲空间,在入队时,在集中出发依次数据的搬移。
2.链式队列:
链表的实现,我们同样需要两个指针:head 指针和 tail 指针
。它们分别指向链表的第一个结点和最后一个结点
。
class Node(object):
"""节点"""
def __init__(self, data=None):
self.data = data
self.next = None
class LkQueue(object):
"""链式队列"""
def __init__(self):
self.front = Node()
self.rear = Node()
self.length = 0
def get_length(self):
"""获取长度"""
return self.length
def is_empty(self):
"""判断是否为空"""
if self.length == 0 :
return True
return False
def en_queue(self, elem):
"""入队操作"""
tmp = Node(elem)
if self.is_empty():
self.front = tmp
self.rear = tmp
else:
self.rear.next = tmp
self.rear = tmp
self.length += 1
def de_queue(self):
"""出队操作"""
if self.is_empty():
raise ValueError("LKQueue is empty!")
else:
del_elem = self.front.data
self.front = self.front.next
self.length -= 1
return del_elem
def show_queue(self):
"""显示队列"""
if self.is_empty():
raise ValueError("LKQueue is empty!")
j = self.length
tmp = self.front
while j > 0:
print(tmp.data)
tmp = tmp.next
j -= 1
lkq = LkQueue()
for i in range(5):
lkq.en_queue(i)
lkq.show_queue()
print('------------------')
lkq.de_queue()
lkq.show_queue()
如图所示:
3.循环队列:
我们把首尾相连,扳成一个环
。
如图所示:
-
队列的大小为8,当前 head = 4,tail = 7;
-
当一个新元素 a 入队时,我们放入小标 为 7 的位置;
-
这时,我们不把 tail 更新为 8,而是在环中后移一位,到下标 0 的位置;
-
在有一个元素 b 入队时, 我们将 b 放入下标为 0 的位置,然后tail 加 1 更新为 1 ;
-
在 a,b 依次入队后,循环队列中元素就变成下面这样;
代码实现:
class CircularQueue(object):
"""循环队列"""
def __init__(self, size):
"""设置队列长度为size"""
self.queue = [""] * size
self.max_size = size
self.start = -1
self.end = -1
def is_full(self):
"""检查循环队列是否已满"""
if (self.end + 1) % self.max_size == self.start:
return True
return False
def is_empty(self):
"""检查循环队列是否为空"""
if self.start == -1 and self.end == -1:
return True
return False
def front(self):
"""从队首获取元素, 如果队列为空, 返回 -1"""
return -1 if self.is_empty() else self.queue[self.start]
def rear(self):
"""获取队尾元素, 如果队列为空, 返回 -1"""
return -1 if self.is_empty() else self.queue[self.end]
def en_queue(self, element):
"""向循环队列插入一个元素, 如果成功插入返回 True"""
if not self.is_full():
self.start = 0
self.end = (self.end + 1) % self.max_size
self.queue[self.end] = element
return True
return False
def de_queue(self):
"""从循环队列中删除一个元素, 如果成功删除, 返回True"""
if not self.is_empty():
if self.start == self.end:
self.start, self.end = -1, -1
else:
self.start = (self.start + 1) % self.max_size
return True
return False
queue = CircularQueue(8)
for i in range(8):
queue.en_queue(i)
4.阻塞队列:
阻塞队列就是在队列基础上增加了阻塞操作
。
简单理解:
- 就是在队列为空的时候,从队头取数据会被阻塞。因为此时还没有数据可取,直到队列中有了数据才能返回;
- 如果队列已经满了,那么插入数据的操作就会被阻塞,直到队列中有空闲位置后再插入数据,然后再返回;
如图所示:
也可以想到"生产者" 跟 “消费者” 的个数,来提高数据的处理效率。
如图所示:
在多线程的情况下,有多个线程同时操作队列,这个时就会存在线程安全
的问题,如何实现一个线程安全队列呢?
5.并发队列:
线程安全的队列,我们叫做并发队列.
最简单方式是直接在 enqueue()、dequeue() 方法上加锁,但是锁粒度大并发度会比较低
,同一时刻仅允许一个存或者取操作。实际上,基于数组的循环队列,利用 CAS 原子操作,可以实现非常高效的并发队列。
参考资料:
《数据结构与算法之美》