使用 Python 构建树的线性层次结构

Posted

技术标签:

【中文标题】使用 Python 构建树的线性层次结构【英文标题】:Building a linear hierarchy of a tree with Python 【发布时间】:2018-10-01 15:43:12 【问题描述】:

有以下json:


    'a': 
        'children': [],
        'name': 'a'
    ,
    'b': 
        'children': [
                'x': 
                    'children': [],
                    'name': 'x'
                
            , 
                'y': 
                    'children': [
                        'z': 
                            'children': [],
                            'name': 'z'
                        
                    ]
                ]
        
    

最终结果应该是:

a 
b -> x
b -> y -> z

我无法将注意力集中在我需要解决这个问题的递归函数上。链表是解决这个问题的方法吗?我的数据中存在未知级别的递归,因此该函数应该只返回任何子节点。我可以递归地列出所有节点,但是跟踪它们是我的问题。

def print_tree(tree, prev=None, child=False):
    for node in tree:
        print(node['name'])
        if len(node['children']):          
            print_tree(node['children'])




print_tree(tree_data)

我在这里缺少什么逻辑来跟踪这一点?

【问题讨论】:

【参考方案1】:

你的代码有很多问题

    JSON 无效,在最后一个 ] 之前缺少一个结束

    您的by 节点没有name

    你的结构不一致:你的每一项数据都是一个节点,children中的每个元素都是一个节点,但是你的数据本身不是一个节点。此外,您的最外层数据使用 'a': ..., 'b': ... 结构,但子级使用[ 'a': ... , 'b': ... ] 结构。

    dict-wrapping 节点使 actual 节点很难取出。即,如果我给你 'x': nodeX ,其中'x' 是未知值,那么你的程序很难提取nodeX

我们首先修复 12

data = \
   'a':  'children': []
         , 'name': 'a'
         
  , 'b':  'children': [  'x':  'children': []
                                , 'name': 'x'
                                
                         
                       ,  'y':  'children': [  'z':  'children': []
                                                       , 'name': 'z'
                                                       
                                                
                                              ]
                                , 'name': 'y' # you missed this
                                
                          # you missed this
                       ]
          , 'name': 'b'  # you missed this
          
  

然后我们通过使用root 节点制作统一结构来修复3

root = \
   'root':  'children': [ k:v for (k,v) in data.items() ]
            , 'name': 'root'
            
  

然后我们用 unwrap_node 助手修复 4

def unwrap_node (wrapped_node):
  node, *_ = wrapped_node.values()
  if 'children' in node and len (node['children']) > 0:
    return  'name': node['name']
           , 'children': [ unwrap_node(n) for n in node['children'] ]
           
  else:
    return node 

现在我们来解决您的问题。我们编写了一个通用的traverse 函数,它只为树中的每个节点生成一个祖先路径(list

def traverse (node, path = []):
  if 'children' in node and len (node['children']) > 0:
    for n in node['children']:
      yield from traverse (n, path + [ node ])
  else:
    yield path + [ node ]

使用每个祖先路径,我们可以很容易地通过name属性加入节点并使用"->"分离

for path in traverse (unwrap_node (root)):
  print (" -> ".join (node['name'] for node in path))

# root -> a
# root -> b -> x
# root -> b -> y -> z

最后,编写print_tree 实现您想要的输出,类似于我们上面的循环。我们也可以过滤掉root -> ...的打印

def print_tree (node):    
  for path in traverse (unwrap_node (node)):
    print (" -> ".join (n['name'] for n in path if n['name'] is not 'root'))

print_tree (root)
# a
# b -> x
# b -> y -> z

如果您解决了 JSON 的严重结构性问题,您就可以避免处理问题

【讨论】:

【参考方案2】:

如果我这样做,我会收集列表中的路径,然后构建字符串。这样做的好处是可以轻松地更改您想要对这些路径执行的操作(例如更改输出格式、将它们传递给另一个函数等),而无需更改您的逻辑。

为此,我将创建一个辅助函数来处理构建路径,并让我计划调用的函数仅用于收集/转换结果。所以像:

# this function collects the paths as lists (e.g. ['b', 'y', 'z']) and returns a list of those paths
def get_paths(tree):
  paths = []
  for branch in tree:
    name = tree[branch]['name']
    children = tree[branch]['children']
    if len(children):
      # this mostly accounts for the fact that the children are a dictionary in a list
      for node in children:
        # get the paths from the children
        sub_paths = get_paths(node)
        # add this element to the beginning of those paths
        for path in sub_paths:
          path.insert(0, name)
        # transfer modified sub-paths to list of paths
        paths.extend(sub_paths)
    else:
      # leaf node, add as a path with one element
      paths.append([name])
  return paths

# this function uses the above function to get the paths and then prints the results as desired
def print_tree(tree):
  paths = get_paths(tree)
  print(paths)
  # do whatever you want with the paths
  for path in paths:
    print(' -> '.join(path))

您的输入(修改为为“y”和“b”添加名称)给出:

a
b -> x
b -> y -> z

【讨论】:

这实现了所需的输出,但它是建立在 kludge 上的 kludge。无论如何,很好的答案和解释。 @user633183 够公平的。我也不喜欢 json 格式,但 OP 可能会也可能不会更改它。现在我认为这种东西正在变得越来越好,但很多人仍然随心所欲地接受其他团队拒绝更新他们过时或不合逻辑的要求。尽管解决这个问题值得称赞。 感谢您的讨论。我真诚地喜欢你的诗。

以上是关于使用 Python 构建树的线性层次结构的主要内容,如果未能解决你的问题,请参考以下文章

数据结构——链队列实现二叉树的层次遍历

从父/子的平面列表构建层次结构对象

二叉树的先序/中序/后序/层次遍历

树的浅析与实现

20172328 2018-2019《Java软件结构与数据结构》第六周学习总结

数据结构:树