佇列(queue)是一種先進先出(First In First Out, FIFO)的資料結構,亦即比較早被放進去的,會比較早就被拿出來。Queue 在生活中最常見的例子就是普通的隊伍,若不考慮插隊或中途離隊等等的特殊情況,則先進入隊伍的人可以先結帳或者先買到票等等,如下:


圖源:木棉花

Queue 的基本操作有加入隊伍(enqueue)和離開隊伍(dequeue);enqueue 時,會從隊伍的尾端(rear/tail)加入元素;dequeue 時,會從隊伍的頭端(front/head)拿出一個元素;此外,你也可以執行其他操作,例如看看隊伍有多長,以及看看隊伍是不是空的等等。

Queue 在 Python 當中,跟 stack 一樣,也可以用 list 來實作:

q = []

q.append(3)
q.append(4)
r = q.pop(0)
r = q.pop(0)
r = q.pop(0) # Leads to error
q.append(567)
r = q.pop(0)
r = q.pop(0) # Leads to error

在上述範例中:

與 stack 相似的是,如果你要使用某些其他語言實作 queue ,或者當你有效能等特殊考量時,也可以先把所需要的空間一次配置完畢;但這次我們需要設立兩個變數,分別代表 queue 的頭端和尾端的位置:

q = [0 for _ in range(5)]
head = 0
tail = 0

# enqueue 3
q[tail] = 3
tail += 1

# enqueue 4
q[tail] = 4
tail += 1

# dequeue
r = q[head]
head += 1
print(r)

# dequeue
r = q[head]
head += 1
print(r)

在上述範例中:

我們把前一個範例包裝成物件導向,並同時解決「一些已知問題」,例如預先配置空間不足,以及 queue 沒東西時的 dequeue 應該回傳特定值或者噴錯誤:

class Queue:
	def __init__(self, init_capacity=10):
		assert k > 0
		self.capacity = init_capacity
		self.q = [None for _ in range(self.capacity)]
		self.head = 0
		self.tail = 0

	def enqueue(self, v):
		self.q[self.tail] = v
		self.tail += 1
		if self.tail == self.capacity:
			self.q += [None for _ in range(self.capacity)]
			self.capacity += self.capacity

	def dequeue(self):
		if self.head == self.tail:
			return None
		r = self.q[self.head]
		self.head += 1
		if self.head >= self.capacity:
			del self.q[:self.capacity]
			self.head -= self.capacity
			self.tail -= self.capacity
		return r


Q = Queue(50)
Q.enqueue(3)
Q.enqueue(4)
print(Q.dequeue()) # 3
print(Q.dequeue()) # 4
print(Q.dequeue()) # None

在上述範例中:

對於 queue 各項操作的時間複雜度,說明則如下:

操作時間複雜度
Enqueue

假設有無限大的連續空間可使用的理想狀況下,因為就只是在指定位置放個東西而已,而陣列的存取操作是 O(1),因此 enqueue 可以直接當作 O(1)。

考慮到需要配置新空間時,即 tail 的值已達到 k 時,則那次的 enqueue 因為有跑個迴圈,所以是 O(k);但因為每 k 次只會碰到那一次,所以整體平均下來的複雜度還是 O(1)。

Dequeue

若不處理空間浪費的話,則最多都是一樣數量的操作步驟,因此是 O(1)。

考慮到需要釋放舊空間時,即 head 的值已達到 k 時,則那次的 dequeue 的刪除可能在系統底層是 O(k);但因為每 k 次只會碰到那一次,所以整體平均下來的複雜度還是 O(1)。

若用 list 的 pop(0) 當作 dequque,則因為 list 的 pop(0) 是 O(n),所以這樣子的 dequeue 是 O(n)。

Size 可以單純透過檢查變數值來進行,因此是 O(1)。
Is_empty