[Python] 實作 BFS (breadth-first search) 演算法

[Python] 實作 BFS (breadth-first search) 演算法

今天在解一個問題,發現需要用圖論中的 BFS 演算法來解~

問題類似下面這樣:每個 task 可能會有零到多個 sub-task,

而 sub-task 又可以再遞迴包含零到多個 sub-task…

但如果上層的 task 已經被標示成不需要跑的時候,希望子孫也都直接標成不要跑~

 

原本自己在實作時,一開始是像這麼寫的:

yielded_tasks_old_rounds = []
while task_list:
yielded_tasks_cur_round = []
kept_tasks = []
for task in task_list:
if not task.parent or task.parent in yielded_tasks_old_rounds:
yielded_tasks_cur_round.append(task)
yield task
else:
kept_tasks.append(task)
yielded_tasks_old_rounds.extend(yielded_tasks_cur_round)
task_list = kept_tasks

 

原本的想法是先走過一輪,把沒有父代的 task 找出來 yield 出去,

在下一輪的時候,把父代是已經被 yield 過的 task 找出來 yield 出去,

持續這種步驟直到 task_list 不再有 task 為止~

這個作法雖然算是 O(n),但是有個嚴重的問題:假設 A, B 兩個 task 在同一層,

那麼 A 和 B 的直接的 children 應該要是 A 的先出現、再出現 B 的 children,

但上面的實作是照原本 task_list 的順序,所以雖然還是廣度優先,但在同一層看起來像是在亂跳…

 

重新研究了一下 BFS (breadth-first search),原來應該是要將上層的 task 放在 queue 裡,

依序把他們的 children 放到 queue 的後面,再依序處理下去~

這個作法也是 O(n),但確保了同一層中先出現的 task,其 children 在下一層也會先出現~

只是這個演算法會需要 parent->children 的關係,因此需要先建立起來,

不像我原本 (錯誤的) 實作是只需要原有的 child->parent 的關係:

dict_task_children = {}
result_list = []
for task in task_list:
if task.parent:
# Construct a dict for mapping task->children tasks
dict_task_children.setdefault(task.parent, []).append(task)
else:
# Initialize result list with tasks without parents in order
result_list.append(task)
i = 0
while i < len(result_list):
task = result_list[i]
yield task
# Add children of current task at end of queue
result_list.extend(dict_task_children.get(task, []))
i += 1

 

結論:蠻有趣的,不過我的演算法該重修了… Orz

 

參考資料:Graph Traversal: Breadth-first Search

 

(本頁面已被瀏覽過 969 次)

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料