2020/09/04 更新,本文末有更新的 BFS,DFS 代码实现,个人感觉更显雕琢、精炼。
Overview
问题
评论树(数据结构):
doc = [
{'id': '100',
'name': "jack",
'children':
[
{'id': '110',
'name': "jack01",
'children':
[
{'id': '111', 'name': "jack02", 'children': []}
]},
{'id': '120',
'name': 'jack03',
'children':
[
{'id': '121', 'name': 'jack04', 'children': []},
{'id': '122', 'name': 'jack05', 'children': []},
{'id': '123', 'name': 'jack06', 'children': []},
]},
]},
{'id': '200', 'name': "tom", 'children': []},
{'id': '300', 'name': "smith", 'children': []},
{'id': '400', 'name': "role", 'children': []}
]
期望结果:
[{'children': [], 'id': '111', 'name': 'jack02'},
{'children': [], 'id': '110', 'name': 'jack01'},
{'children': [], 'id': '121', 'name': 'jack04'},
{'children': [], 'id': '122', 'name': 'jack05'},
{'children': [], 'id': '123', 'name': 'jack06'},
{'children': [], 'id': '120', 'name': 'jack03'},
{'children': [], 'id': '100', 'name': 'jack'},
{'children': [], 'id': '200', 'name': 'tom'},
{'children': [], 'id': '300', 'name': 'smith'},
{'children': [], 'id': '400', 'name': 'role'}]
Solution - 深度优先 - 递归实现
算法实现:
def DFTraversal(root):
result = list()
if root['children']:
for parent in root['children']:
__ret = DFTraversal(parent)
for _ in __ret:
if _:
result.append(_)
__tmp = copy(parent)
__tmp['children'] = []
result.append(__tmp)
return result
else:
return result
测试:
from copy import copy
from copy import deepcopy
root = {'children': doc}
ret = DFTraversal(deepcopy(root))
结果:
要点:
- 本方案为递归解决方案,
- 因为使用了递归,所以无需维护深度优先遍历的栈 - 调用函数就是堆栈。
- 因为用例关系,所以无需
.deepcopy
(代码中的.deepcopy
可以不使用,直接调用DFS_traversal(root)
亦可 )
如果字典中的children
外有嵌套,则需要使用.deepcopy
(或者是手动复制除children
外的键值副本)注:针对本例而言,实际上
.copy
也是可以不需要的。不过如果不使用.copy
,那么原树结构的数据(doc
)会被修改(DFTraveral
函数原地修改传递的参数内数据)。
同时因为使用.copy
个人认为可能会比较好容易理解。而不使用.copy
需要对 Python 的数据/变量有清楚的认识。
紧凑版算法实现(可能不容易被理解):
def DFTraversal_tight(root):
result = list()
if root['children']:
for parent in root['children']:
result.extend([_ for _ in DFTraversal_tight(parent) if _])
parent['children'] = []
result.append(parent)
return result
Solution - 深度优先 - 迭代实现
def DFTraversal(root):
result = list()
stack = Stack()
parent = root if isinstance(root, dict) else {'children': root}
try:
while True:
while parent['children']:
child = parent['children'].pop()
stack.push(parent)
parent = child
param = parent
result.append(param)
parent = stack.pick()
if parent['children']: # brother != NULL
brother = parent['children'].pop()
else:
result.append(stack.pop())
parent = stack.pick()
while not parent['children']:
result.append(stack.pop())
parent = stack.pick()
brother = parent['children'].pop()
parent = brother
except IndexError:
if isinstance(root, dict):
result.pop()
return result
运行结构:
Solution - 广度优先 - 非递归实现
就本文的 doc
数据而言,是一群友的爬虫程序爬的多级评论要保存到数据库中。
其在群里提问引起了我的兴趣所以我就写写代码作为算法练习。
如果是需要保留评论的多级结构的话,那么使用广度优先遍历来解析数据并保存会比深度优先遍历简单一些。
算法实现:
def BFTraversal(root):
from collections import deque
from copy import copy
__result = list()
__traversal_q = deque()
__traversal_q += root['children']
while __traversal_q:
__perant = __traversal_q.popleft()
__tmp = copy(__perant)
__tmp['children'] = list()
__result.append(__tmp)
if __perant['children']:
__traversal_q += __perant['children']
return __result
测试:
其中
doc
就是本文最开头的测试数据。
要点:
- 非递归实现
- 因为数据结构是“树”,而非“图”,所以算法实现代码中省略了“排除访问过的节点”这一处(树结构不存在环路)。
- 算法细节可以参考后文 Reference -> 1.
Solution - BFT - 递归实现
实现代码:
def BFS_traversal_recurrence(root):
from copy import copy
__result = list()
for __perant in root['children']:
__tmp = copy(__perant)
__tmp['children'] = list()
__result.append(__tmp)
if __perant['children']:
__result.extend([_ for _ in BFS_traversal_recurrence(__perant) if _])
return __result
测试:
2020/09/04 更新 - 代码更经雕琢(简练)
DFS
递归版本:
def dfs_1(parent):
if not parent['children']: return [parent]
ret = []
for p in parent['children']: # 1. Depth First Search,
ret.extend(dfs_1(p)) # 有下一层,先递归调用处理下一层
parent['children'].clear() # traversed children, had been moved to `ret`(list)
ret.append(parent) # 2. 处理完所有子节点,然后处理本节点
return ret
迭代版本:
def dfs_2(parent):
if not parent['children']: return [parent]
ret = []
_q = [parent]
while _q:
p = _q[-1] # .pick()
if p['children']:
for _ in range(len(p['children'])):
_q.append(p['children'].pop())
else:
ret.append(_q.pop())
return ret
遍历测试:
def traverse_dfs(parents):
ans = []
for p in parents:
ans.extend(dfs_1(p)) # ans.extend(dfs_2(p))
for p in ans:
print(p)
import copy
traverse_dfs( copy.deepcopy(doc) )
BFS
迭代实现(BFS 并不适合用递归实现):
def bfs(parent):
if not parent['children']: return [parent]
from collections import deque
ret = []
q = deque()
q.append(parent) # 1. 将第 0 层加到 queue 中
while q:
p = q.popleft() # 2. 从左弹出(FIFO),同一层按
for c in p['children']: # 添加顺序,放到 answer 中
q.append(c) # 1. 将下一层全部添加到 queue 中,
p['children'].clear() # Note: 本层放入 ans 前,先执行 1.
ret.append(p) # 3. 将本层挨个(`while q`) 放入 ans
return ret
遍历测试:
def traverse_bfs(parents):
ans = []
for p in parents:
ans.extend(bfs(p))
for p in ans:
print(p)
traverse_bfs( copy.deepcopy(doc) )
Reference
- 《图解算法》 - CH6 广度优先搜索 @p90