數據結構: 隊列 講解
一、概念:
先進者先出,這就是 隊列。
把它想象成排隊買票,先來的先買,後來的人只能站末尾,不允許插隊。
隊列跟棧非常相似,支持的操作也很有限,最基本的操作也是兩個:
- 入隊 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)
。 -
優化思路,在出隊時,不用搬移數據,如果沒有空閒空間,在入隊時,在集中出發依次數據的搬移。
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 原子操作,可以實現非常高效的併發隊列。
參考資料:
《數據結構與算法之美》