各位應該聽過一個經典問題,即給你五公升和三公升的杯子,要量出四公升的水。本篇將介紹如何利用 queue 來解決這個問題。我們首先對此問題,做比較正式的描述如下(若有興趣,可以自己嘗試其他擴展):

然後你可能會想要胡亂操作一通碰碰運氣,或者你數學夠好的話,可能知道可以利用輾轉相除法的過程找到一組解;不過前者可能在腦袋空空又很倒楣的情況下一直繞圈圈找不到解,後者則因為相當於把三種操作依固定順序進行,所以不保證是最佳解。

透過 queue 來進行,則保證可以在有解的情況下,找到最佳解。具體步驟如下:

  1. 我們從一個固定狀態,例如代表兩杯都沒有水的 (0, 0) 出發。先把該狀態放進 queue 當中。
  2. 反覆的 dequeue 一個出來,並計算出所有可能的下一狀態。
  3. 對上一步的每個狀態做檢查,若當中有 c 則結束尋找,反之則將其 enqueue。

具體的實作如下:

a = 5
b = 3
c = 4

q = [(0, 0, -1)]
head = 0
while True:
	a_curr, b_curr, _ = q[head]
	if c in (a_curr, b_curr):
		break
	# Fill A
	q.append((a, b_curr, head))
	# Fill B
	q.append((a_curr, b, head))
	# Empty A
	q.append((0, b_curr, head))
	# Empty B
	q.append((a_curr, 0, head))
	# Pour A to B
	if a_curr >= b - b_curr:
		a_new = a_curr - (b - b_curr)
		b_new = b
	else:
		a_new = 0
		b_new = b_curr + a_curr
	q.append((a_new, b_new, head))
	# Pour B to A
	if b_curr >= a - a_curr:
		a_new = a
		b_new = b_curr - (a - a_curr)
	else:
		a_new = a_curr + b_curr
		b_new = 0
	q.append((a_new, b_new, head))
	# Update head
	head += 1

s = []
while head >= 0:
	s.append(q[head][:2])
	head = q[head][2]
print(s[::-1])

在上述範例中:

這樣子的演算法,有個正式名稱是「廣度優先搜尋(Breadth-First Search, BFS)」,其時間複雜度在本範例而言,由於:

因此,時間複雜度是 O(6ab)。

Question: 如果有避免把走過的狀態 enqueue,則時間複雜度會是多少?提示:檢查某個元素在不在某個群體當中有不同的做法,時間複雜度也各不相同;若使用 list 的話是 O(n),使用 dict 或 set 的話則一般會視為 O(1)。