【算法】【Python】遍历“评论”树 - 递归/迭代 x 广度/深度优先 4 种实现

2020/09/04 更新,本文末有更新的 BFS,DFS 代码实现,个人感觉更显雕琢、精炼。



问题

评论树(数据结构):

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))

结果:
n/a

要点:

  • 本方案为递归解决方案,
  • 因为使用了递归,所以无需维护深度优先遍历的栈 - 调用函数就是堆栈。
  • 因为用例关系,所以无需 .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

运行结构:

n/a



Solution - 广度优先 - 非递归实现

就本文的 doc 数据而言,是一群友的爬虫程序爬的多级评论要保存到数据库中。
其在群里提问引起了我的兴趣所以我就写写代码作为算法练习。
n/a
如果是需要保留评论的多级结构的话,那么使用广度优先遍历来解析数据并保存会比深度优先遍历简单一些。


算法实现:

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

测试:

n/a

其中 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

测试:

n/a



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

  1. 《图解算法》 - CH6 广度优先搜索 @p90

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值