如何从邻接列表构建嵌套树结构?

时间:2020-09-12 20:32:15

标签: python recursion tree traversal

考虑到我有

  • 名为A的相邻键(子级-父级)的列表
  • 名为Tree的树类,存储了自己的节点键(整数)和子级(类)

A = [(61, 66), (50, 61), (68, 61), (33, 61), (57, 66), (72, 66), (37, 68), (71, 33), (6, 50), (11, 37), (5, 37)]

class Tree:
    def __init__(self, node, *children):
        self.node = node
        if children: self.children = children
        else: self.children = []
    
    def __str__(self): 
        return "%s" % (self.node)
    def __repr__(self):
        return "%s" % (self.node)

    def __getitem__(self, k):
        if isinstance(k, int) or isinstance(k, slice): 
            return self.children[k]
        if isinstance(k, str):
            for child in self.children:
                if child.node == k: return child

    def __iter__(self): return self.children.__iter__()

    def __len__(self): return len(self.children)

如何构建一个Tree对象,使其根据邻接关系封装所有内部树?(如下所示)

t = Tree(66, 
        Tree(72), 
        Tree(57), 
        Tree(61, 
            Tree(33,
                Tree(71)), 
            Tree(50, 
                Tree(6)), 
            Tree(68, 
                Tree(37, 
                    Tree(11), Tree(5)))))

我当时正在考虑以递归方式创建树,但无法弄清楚如何正确遍历和填充树。这是我失败的尝试:

from collections import defaultdict

# Create a dictionary: key = parent, values = children
d = defaultdict(list)
for child, parent in A:
    d[parent].append(child)

# Failed attempt
def build_tree(k):    
    if k in d:
        tree = Tree(k, d[k]) #1st issue: should input a Tree() as 2nd parameter
        for child in d[k]:
            build_tree(child) #2nd issue: should populate tree, not iterate recursively over children keys

#I know that the root node is 66.
full_tree = build_tree(66)
        

3 个答案:

答案 0 :(得分:2)

您在这段代码中提到了两个问题:

    tree = Tree(k, d[k]) #1st issue: should input a Tree() as 2nd parameter
    for child in d[k]:
        build_tree(child) #2nd issue: should populate tree, not iterate recursively over children keys

您可以通过将for循环本质上以列表理解的形式移动到第二个参数中并飞溅该列表以使其成为参数来解决它们。然后确保您的递归函数返回创建的树:

    return Tree(k, 
        *[build_tree(child) for child in d[k]]
    )

更多想法

与您的问题无关,但是这里还有一些您可以使用的想法。

  • 建议使代码成为一个函数,您可以将A作为参数传递给该函数,以使字典的作用域只是该函数的局部作用,而不会浪费全局作用域。 / p>

  • 由于此功能与Tree类密切相关,因此最好将其定义为类中的静态方法或类方法。

  • 当树具有(子,父)元组时,这些元组将隐式定义哪个节点是根,因此可以省略将字面量66传递给函数。该功能应该能够自己找出根。创建字典时,它还可以收集哪些节点具有父节点。这样,根就是不在该集合中的节点。

因此,所有这些都将得到:

from collections import defaultdict

class Tree:
    def __init__(self, node, *children):
        self.node = node
        self.children = children if children else []
    
    def __str__(self): 
        return "%s" % (self.node)
    
    def __repr__(self):
        return "%s" % (self.node)

    def __getitem__(self, k):
        if isinstance(k, int) or isinstance(k, slice): 
            return self.children[k]
        if isinstance(k, str):
            for child in self.children:
                if child.node == k:
                    return child

    def __iter__(self):
        return self.children.__iter__()

    def __len__(self):
        return len(self.children)

    @classmethod
    def from_pairs(Cls, pairs):
        # Turn pairs into nested dictionary
        d = defaultdict(list)
        children = set()
        for child, parent in pairs:
            d[parent].append(child)
            # collect nodes that have a parent
            children.add(child)
        
        # Find root: it does not have a parent
        root = next(parent for parent in d if parent not in children)

        # Build nested Tree instances recursively from the dictionary
        def subtree(k):
            return Cls(k, *[subtree(child) for child in d[k]])

        return subtree(root)

# Sample run
A = [(61, 66), (50, 61), (68, 61), (33, 61), (57, 66), (72, 66), (37, 68), (71, 33), (6, 50), (11, 37), (5, 37)]

tree = Tree.from_pairs(A)

答案 1 :(得分:1)

您已经关闭。关键是将新节点返回到父节点,并将其附加到父节点的子节点列表。如果您的父级列表在初始化时是固定的,则只需使用一个临时列表,然后在访问并创建子级后创建父级。

这是一个最小的例子:

from collections import defaultdict, namedtuple

def build_tree(tree, root):
    if root:
        return Node(root, [build_tree(tree, x) for x in tree.get(root, [])])

def print_tree(root, indent=0):
    if root:
        print(" " * indent + str(root.val))
        
        for child in root.children:
            print_tree(child, indent + 2)

if __name__ == "__main__":
    A = [(61, 66), (50, 61), (68, 61), (33, 61), (57, 66), (72, 66), 
         (37, 68), (71, 33), (6, 50), (11, 37), (5, 37)]
    Node = namedtuple("Node", "val children")
    nodes = defaultdict(list)
    
    for child, parent in A:
        nodes[parent].append(child)

    print_tree(build_tree(nodes, 66))

输出:

66
  61
    50
      6
    68
      37
        11
        5
    33
      71
  57
  72

答案 2 :(得分:0)

这里是学习可重用模块和相互递归的机会。此答案中的解决方案无需修改another answer 1 中编写的模块即可解决您的特定问题。指出这一点很重要,因为它显示了泛型函数如何促进代码重用并减少错误爬入程序的机会。

首先,我们将定义特定于您的(id, parent)输入结构形状的函数-

# main.py

def id(node):
  return node[0]

def parent(node):
  return node[1]

n = (12,34)

id(n)     # => 12
parent(n) # => 34

也许知道根节点是66,但是这对于我们的程序来说很难推断并且很难定义。让我们在输入数据中明确包含(66, None),其中parent=None表示 root 节点-

A = \
  [ (61, 66), (50, 61), (68, 61), (33, 61)
  , (57, 66), (72, 66), (37, 68), (71, 33)
  , (6, 50), (11, 37), (5, 37), (66, None) # don't forget root node, 66
  ]

现在我们可以使用tree模块轻松构建我们的树-

# main.py

from tree import tree

def id #...
def parent #...

A = [ ... ]

B = tree \
  ( A                                # list of nodes
  , parent                           # foreign key
  , lambda node, children:           # node reconstructor
      (id(node), children(id(node))) # primary key 
  )

print(B)
# [(66, [(61, [(50, [(6, [])]), (68, [(37, [(11, []), (5, [])])]), (33, [(71, [])])]), (57, []), (72, [])])]

请注意,tree与输入的形状无关。可以使用 any 节点结构。 tree函数非常灵活,它使我们能够以与输入节点完全不同的形状构造树节点-

# main.py

from tree import tree
from json import dumps

def id #...
def parent #...

A = [ ... ]

C = tree \
  ( A
  , parent
  , lambda node, children:
      dict([("id", id(node)), ("children", children(id(node)))])
  )

print(dumps(C))
[ { "id": 66
  , "children":
      [ { "id": 61
        , "children":
            [ { "id": 50
              , "children":
                  [ { "id": 6, "children": [] }
                  ]
              }
            , { "id": 68
              , "children":
                [ { "id": 37
                  , "children":
                      [ { "id": 11, "children": [] }
                      , { "id": 5, "children": [] }
                      ]
                  }
                ]
              }
            , { "id": 33
              , "children":
                  [ { "id": 71, "children": [] }
                  ]
              }
            ]
        }
      , { "id": 57, "children": [] }
      , { "id": 72, "children": [] }
      ]
  }
]

现在,我们可以看看tree的实现。注意tree如何不对输入节点的形状做任何假设-

# tree.py

from index import index, get

def empty():
  return []

def tree (all, indexer, maker, root = None):
  mem = index(all, indexer)

  def many(all):
    return list(map(one, all))
  
  def one(single):
    return maker(single, lambda r: many(get(mem, r, empty())))
  
  return many(get(mem, root))

我们对tree的实现取决于另一个模块index。将数据结构(例如 index )以及对这些数据结构进行操作的函数进行分组是一种在模块之间划分边界的好方法。这里也没有关于输入形状的假设-

# index.py

from functools import reduce

def empty():
  return {}

def update(t, k, f):
  if k in t:
    return { **t, k: f(get(t, k)) }
  else:
    return { **t, k: f() }

def get(t, k, default = None):
  if k in t:
    return t[k]
  else:
    return default

def append(t, k, v):
  return update(t, k, lambda r = []: [ *r, v ])

def index(ls, indexer):
  return reduce \
    ( lambda t, v: append(t, indexer(v), v)
    , ls
    , empty()
    )

通过在浏览器中运行我们的结果来验证结果: run this program on repl.it


1 模块已移植到Python。用JavaScript编写的原始程序。

相关问题