代码之家  ›  专栏  ›  技术社区  ›  Costo

如何从扁平结构中有效地构建一棵树?

  •  118
  • Costo  · 技术社区  · 16 年前

    我有一堆扁平的物体。这些对象具有 ID 和A ParentID 使它们可以排列在树上。它们没有特别的顺序。 各 父节点 属性不一定与 身份证件 在结构中。因此,它们可能是从这些物体中长出来的几棵树。

    如何处理这些对象以创建结果树?

    我离解决方案不远,但我确信它远不是最佳的…

    我需要创建这些树,然后按适当的顺序将数据插入数据库。

    没有循环引用。当parentID==null或在其他对象中找不到parentID时,节点是rootnode。

    15 回复  |  直到 6 年前
        1
  •  96
  •   Guido Preite    12 年前

    将对象的ID存储在映射到特定对象的哈希表中。枚举所有对象,找到它们的父对象(如果存在),并相应地更新它的父指针。

    class MyObject
    { // The actual object
        public int ParentID { get; set; }
        public int ID { get; set; }
    }
    
    class Node
    {
        public List<Node> Children = new List<Node>();
        public Node Parent { get; set; }
        public MyObject AssociatedObject { get; set; }
    }
    
    IEnumerable<Node> BuildTreeAndGetRoots(List<MyObject> actualObjects)
    {
        Dictionary<int, Node> lookup = new Dictionary<int, Node>();
        actualObjects.ForEach(x => lookup.Add(x.ID, new Node { AssociatedObject = x }));
        foreach (var item in lookup.Values) {
            Node proposedParent;
            if (lookup.TryGetValue(item.AssociatedObject.ParentID, out proposedParent)) {
                item.Parent = proposedParent;
                proposedParent.Children.Add(item);
            }
        }
        return lookup.Values.Where(x => x.Parent == null);
    }
    
        2
  •  27
  •   Morgoth Jerry Coffin    8 年前

    根据梅尔达德阿夫沙里的回答和安德鲁·汉伦关于加速的评论,这是我的看法。

    与原始任务的重要区别:根节点的id==parentid。

    class MyObject
    {   // The actual object
        public int ParentID { get; set; }
        public int ID { get; set; }
    }
    
    class Node
    {
        public List<Node> Children = new List<Node>();
        public Node Parent { get; set; }
        public MyObject Source { get; set; }
    }
    
    List<Node> BuildTreeAndGetRoots(List<MyObject> actualObjects)
    {
        var lookup = new Dictionary<int, Node>();
        var rootNodes = new List<Node>();
    
        foreach (var item in actualObjects)
        {
            // add us to lookup
            Node ourNode;
            if (lookup.TryGetValue(item.ID, out ourNode))
            {   // was already found as a parent - register the actual object
                ourNode.Source = item;
            }
            else
            {
                ourNode = new Node() { Source = item };
                lookup.Add(item.ID, ourNode);
            }
    
            // hook into parent
            if (item.ParentID == item.ID)
            {   // is a root node
                rootNodes.Add(ourNode);
            }
            else
            {   // is a child row - so we have a parent
                Node parentNode;
                if (!lookup.TryGetValue(item.ParentID, out parentNode))
                {   // unknown parent, construct preliminary parent
                    parentNode = new Node();
                    lookup.Add(item.ParentID, parentNode);
                }
                parentNode.Children.Add(ourNode);
                ourNode.Parent = parentNode;
            }
        }
    
        return rootNodes;
    }
    
        3
  •  26
  •   Morgoth Jerry Coffin    8 年前

    下面是一个简单的javascript算法,用于将一个平面表解析为n次运行的父/子树结构:

    var table = [
        {parent_id: 0, id: 1, children: []},
        {parent_id: 0, id: 2, children: []},
        {parent_id: 0, id: 3, children: []},
        {parent_id: 1, id: 4, children: []},
        {parent_id: 1, id: 5, children: []},
        {parent_id: 1, id: 6, children: []},
        {parent_id: 2, id: 7, children: []},
        {parent_id: 7, id: 8, children: []},
        {parent_id: 8, id: 9, children: []},
        {parent_id: 3, id: 10, children: []}
    ];
    
    var root = {id:0, parent_id: null, children: []};
    var node_list = { 0 : root};
    
    for (var i = 0; i < table.length; i++) {
        node_list[table[i].id] = table[i];
        node_list[table[i].parent_id].children.push(node_list[table[i].id]);
    }
    
    console.log(root);
    
        4
  •  7
  •   minkwe    8 年前

    Python溶液

    def subtree(node, relationships):
        return {
            v: subtree(v, relationships) 
            for v in [x[0] for x in relationships if x[1] == node]
        }
    

    例如:

    # (child, parent) pairs where -1 means no parent    
    flat_tree = [
         (1, -1),
         (4, 1),
         (10, 4),
         (11, 4),
         (16, 11),
         (17, 11),
         (24, 17),
         (25, 17),
         (5, 1),
         (8, 5),
         (9, 5),
         (7, 9),
         (12, 9),
         (22, 12),
         (23, 12),
         (2, 23),
         (26, 23),
         (27, 23),
         (20, 9),
         (21, 9)
        ]
    
    subtree(-1, flat_tree)
    

    生产:

    {
        "1": {
            "4": {
                "10": {}, 
                "11": {
                    "16": {}, 
                    "17": {
                        "24": {}, 
                        "25": {}
                    }
                }
            }, 
            "5": {
                "8": {}, 
                "9": {
                    "20": {}, 
                    "12": {
                        "22": {}, 
                        "23": {
                            "2": {}, 
                            "27": {}, 
                            "26": {}
                        }
                    }, 
                    "21": {}, 
                    "7": {}
                }
            }
        }
    }
    
        5
  •  5
  •   nu11ptr    8 年前

    JS版本,返回一个根或一个根数组,每个根数组都将具有包含相关子数组的子数组属性。不依赖有序的输入,适当地压缩,并且不使用递归。享受!

    // creates a tree from a flat set of hierarchically related data
    var MiracleGrow = function(treeData, key, parentKey)
    {
        var keys = [];
        treeData.map(function(x){
            x.Children = [];
            keys.push(x[key]);
        });
        var roots = treeData.filter(function(x){return keys.indexOf(x[parentKey])==-1});
        var nodes = [];
        roots.map(function(x){nodes.push(x)});
        while(nodes.length > 0)
        {
    
            var node = nodes.pop();
            var children =  treeData.filter(function(x){return x[parentKey] == node[key]});
            children.map(function(x){
                node.Children.push(x);
                nodes.push(x)
            });
        }
        if (roots.length==1) return roots[0];
        return roots;
    }
    
    
    // demo/test data
    var treeData = [
    
        {id:9, name:'Led Zep', parent:null},
        {id:10, name:'Jimmy', parent:9},
        {id:11, name:'Robert', parent:9},
        {id:12, name:'John', parent:9},
    
        {id:8, name:'Elec Gtr Strings', parent:5},
        {id:1, name:'Rush', parent:null},
        {id:2, name:'Alex', parent:1},
        {id:3, name:'Geddy', parent:1},
        {id:4, name:'Neil', parent:1},
        {id:5, name:'Gibson Les Paul', parent:2},
        {id:6, name:'Pearl Kit', parent:4},
        {id:7, name:'Rickenbacker', parent:3},
    
        {id:100, name:'Santa', parent:99},
        {id:101, name:'Elf', parent:100},
    
    ];
    var root = MiracleGrow(treeData, "id", "parent")
    console.log(root)
    
        6
  •  2
  •   codeBelt    7 年前

    在这里找到了一个很棒的javascript版本: http://oskarhane.com/create-a-nested-array-recursively-in-javascript/

    假设您有这样的数组:

    const models = [
        {id: 1, title: 'hello', parent: 0},
        {id: 2, title: 'hello', parent: 0},
        {id: 3, title: 'hello', parent: 1},
        {id: 4, title: 'hello', parent: 3},
        {id: 5, title: 'hello', parent: 4},
        {id: 6, title: 'hello', parent: 4},
        {id: 7, title: 'hello', parent: 3},
        {id: 8, title: 'hello', parent: 2}
    ];
    

    您希望像这样嵌套对象:

    const nestedStructure = [
        {
            id: 1, title: 'hello', parent: 0, children: [
                {
                    id: 3, title: 'hello', parent: 1, children: [
                        {
                            id: 4, title: 'hello', parent: 3, children: [
                                {id: 5, title: 'hello', parent: 4},
                                {id: 6, title: 'hello', parent: 4}
                            ]
                        },
                        {id: 7, title: 'hello', parent: 3}
                    ]
                }
            ]
        },
        {
            id: 2, title: 'hello', parent: 0, children: [
                {id: 8, title: 'hello', parent: 2}
            ]
        }
    ];
    

    这里是一个递归函数,可以实现它。

    function getNestedChildren(models, parentId) {
        const nestedTreeStructure = [];
        const length = models.length;
    
        for (let i = 0; i < length; i++) { // for-loop for perf reasons, huge difference in ie11
            const model = models[i];
    
            if (model.parent == parentId) {
                const children = getNestedChildren(models, model.id);
    
                if (children.length > 0) {
                    model.children = children;
                }
    
                nestedTreeStructure.push(model);
            }
        }
    
        return nestedTreeStructure;
    }
    

    Usuage:

    const models = [
        {id: 1, title: 'hello', parent: 0},
        {id: 2, title: 'hello', parent: 0},
        {id: 3, title: 'hello', parent: 1},
        {id: 4, title: 'hello', parent: 3},
        {id: 5, title: 'hello', parent: 4},
        {id: 6, title: 'hello', parent: 4},
        {id: 7, title: 'hello', parent: 3},
        {id: 8, title: 'hello', parent: 2}
    ];
    const nestedStructure = getNestedChildren(models, 0);
    
        7
  •  1
  •   Henrik Paul    16 年前

    在我看来,这个问题很模糊,我可能会创建一个从ID到实际对象的映射。在伪Java中(我没有检查它是否工作/编译),它可能有点像:

    Map<ID, FlatObject> flatObjectMap = new HashMap<ID, FlatObject>();
    
    for (FlatObject object: flatStructure) {
        flatObjectMap.put(object.ID, object);
    }
    

    查找每个家长:

    private FlatObject getParent(FlatObject object) {
        getRealObject(object.ParentID);
    }
    
    private FlatObject getRealObject(ID objectID) {
        flatObjectMap.get(objectID);
    }
    

    通过重用 getRealObject(ID) 在对象和对象集合(或其ID)之间进行映射时,您也会得到父对象和子对象映射。

        8
  •  1
  •   Arithmomaniac    8 年前

    我用C语言编写了一个通用的解决方案,大致基于@mehrdad afshari答案:

    void Example(List<MyObject> actualObjects)
    {
      List<TreeNode<MyObject>> treeRoots = actualObjects.BuildTree(obj => obj.ID, obj => obj.ParentID, -1);
    }
    
    public class TreeNode<T>
    {
      public TreeNode(T value)
      {
        Value = value;
        Children = new List<TreeNode<T>>();
      }
    
      public T Value { get; private set; }
      public List<TreeNode<T>> Children { get; private set; }
    }
    
    public static class TreeExtensions
    {
      public static List<TreeNode<TValue>> BuildTree<TKey, TValue>(this IEnumerable<TValue> objects, Func<TValue, TKey> keySelector, Func<TValue, TKey> parentKeySelector, TKey defaultKey = default(TKey))
      {
        var roots = new List<TreeNode<TValue>>();
        var allNodes = objects.Select(overrideValue => new TreeNode<TValue>(overrideValue)).ToArray();
        var nodesByRowId = allNodes.ToDictionary(node => keySelector(node.Value));
    
        foreach (var currentNode in allNodes)
        {
          TKey parentKey = parentKeySelector(currentNode.Value);
          if (Equals(parentKey, defaultKey))
          {
            roots.Add(currentNode);
          }
          else
          {
            nodesByRowId[parentKey].Children.Add(currentNode);
          }
        }
    
        return roots;
      }
    }
    
        9
  •  1
  •   Joel Malone    7 年前

    对于任何对Eugene解决方案的C版感兴趣的人,请注意 节点列表 是作为地图访问的,因此请改用字典。

    请记住,此解决方案仅在 桌子 被排序 亲本 .

    var table = new[]
    {
        new Node { parent_id = 0, id = 1 },
        new Node { parent_id = 0, id = 2 },
        new Node { parent_id = 0, id = 3 },
        new Node { parent_id = 1, id = 4 },
        new Node { parent_id = 1, id = 5 },
        new Node { parent_id = 1, id = 6 },
        new Node { parent_id = 2, id = 7 },
        new Node { parent_id = 7, id = 9 },
        new Node { parent_id = 8, id = 9 },
        new Node { parent_id = 3, id = 10 },
    };
    
    var root = new Node { id = 0 };
    var node_list = new Dictionary<int, Node>{
        { 0, root }
    };
    
    foreach (var item in table)
    {
        node_list.Add(item.id, item);
        node_list[item.parent_id].children.Add(node_list[item.id]);
    }
    

    节点定义如下。

    class Node
    {
        public int id { get; set; }
        public int parent_id { get; set; }
        public List<Node> children = new List<Node>();
    }
    
        10
  •  0
  •   Robert Elwell    16 年前

    你只使用那些属性吗?如果不是,那么最好创建一个子节点数组,在该数组中,您可以循环使用所有这些对象一次来构建这些属性。从那里,选择具有子节点但没有父节点的节点,并从上到下迭代地构建树。

        11
  •  0
  •   nes1983    16 年前

    我可以用4行代码和O(n log n)时间来完成这项工作,假设字典类似于treemap。

    dict := Dictionary new.
    ary do: [:each | dict at: each id put: each].
    ary do: [:each | (dict at: each parent) addChild: each].
    root := dict at: nil.
    

    编辑 : 好吧,现在我读到一些parentid是假的,所以忘记上面的内容,这样做:

    dict := Dictionary new.
    dict at: nil put: OrderedCollection new.
    ary do: [:each | dict at: each id put: each].
    ary do: [:each | 
        (dict at: each parent ifAbsent: [dict at: nil]) 
              add: each].
    roots := dict at: nil.
    
        12
  •  0
  •   SQLHack    16 年前

    大多数答案都假设您希望在数据库之外进行此操作。如果您的树本质上是相对静态的,并且您只需要以某种方式将树映射到数据库中,那么您可能需要考虑在数据库端使用嵌套集表示。Joe Celko(或 here 以获取Celko的概述)。

    如果仍然绑定到Oracle DBS,请检查它们的Connect By以获得直接的SQL方法。

    无论采用哪种方法,您都可以在将数据加载到数据库之前完全跳过对树的映射。我只是想把它作为一种选择,它可能完全不适合你的特殊需要。原始问题的整个“正确顺序”部分在某种程度上意味着您出于某种原因需要在数据库中“正确”顺序?这可能也会促使我去处理那里的树木。

        13
  •  0
  •   Aske B.    8 年前

    这与询问者所寻找的并不完全相同,但我很难将我的头绕在这里提供的含糊不清的答案周围,我仍然认为这个答案适合标题。

    我的答案是将一个平面结构映射到一个直接位于对象树上的对象树,其中您所拥有的就是 ParentID 在每个对象上。 父节点 null 0 如果它是根。与提问者相反,我认为一切都有效 父节点 的指向列表中的其他内容:

    var rootNodes = new List<DTIntranetMenuItem>();
    var dictIntranetMenuItems = new Dictionary<long, DTIntranetMenuItem>();
    
    //Convert the flat database items to the DTO's,
    //that has a list of children instead of a ParentID.
    foreach (var efIntranetMenuItem in flatIntranetMenuItems) //List<tblIntranetMenuItem>
    {
        //Automapper (nuget)
        DTIntranetMenuItem intranetMenuItem =
                                       Mapper.Map<DTIntranetMenuItem>(efIntranetMenuItem);
        intranetMenuItem.Children = new List<DTIntranetMenuItem>();
        dictIntranetMenuItems.Add(efIntranetMenuItem.ID, intranetMenuItem);
    }
    
    foreach (var efIntranetMenuItem in flatIntranetMenuItems)
    {
        //Getting the equivalent object of the converted ones
        DTIntranetMenuItem intranetMenuItem = dictIntranetMenuItems[efIntranetMenuItem.ID];
    
        if (efIntranetMenuItem.ParentID == null || efIntranetMenuItem.ParentID <= 0)
        {
            rootNodes.Add(intranetMenuItem);
        }
        else
        {
            var parent = dictIntranetMenuItems[efIntranetMenuItem.ParentID.Value];
            parent.Children.Add(intranetMenuItem);
            //intranetMenuItem.Parent = parent;
        }
    }
    return rootNodes;
    
        14
  •  0
  •   joegiralt    6 年前

    下面是Ruby实现:

    它将按属性名或方法调用的结果分类。

    CatalogGenerator = ->(depth) do
      if depth != 0
        ->(hash, key) do
          hash[key] = Hash.new(&CatalogGenerator[depth - 1])
        end
      else
        ->(hash, key) do
          hash[key] = []
        end
      end
    end
    
    def catalog(collection, root_name: :root, by:)
      method_names = [*by]
      log = Hash.new(&CatalogGenerator[method_names.length])
      tree = collection.each_with_object(log) do |item, catalog|
        path = method_names.map { |method_name| item.public_send(method_name)}.unshift(root_name.to_sym)
      catalog.dig(*path) << item
      end
      tree.with_indifferent_access
    end
    
     students = [#<Student:0x007f891d0b4818 id: 33999, status: "on_hold", tenant_id: 95>,
     #<Student:0x007f891d0b4570 id: 7635, status: "on_hold", tenant_id: 6>,
     #<Student:0x007f891d0b42c8 id: 37220, status: "on_hold", tenant_id: 6>,
     #<Student:0x007f891d0b4020 id: 3444, status: "ready_for_match", tenant_id: 15>,
     #<Student:0x007f8931d5ab58 id: 25166, status: "in_partnership", tenant_id: 10>]
    
    catalog students, by: [:tenant_id, :status]
    
    # this would out put the following
    {"root"=>
      {95=>
        {"on_hold"=>
          [#<Student:0x007f891d0b4818
            id: 33999,
            status: "on_hold",
            tenant_id: 95>]},
       6=>
        {"on_hold"=>
          [#<Student:0x007f891d0b4570 id: 7635, status: "on_hold", tenant_id: 6>,
           #<Student:0x007f891d0b42c8
            id: 37220,
            status: "on_hold",
            tenant_id: 6>]},
       15=>
        {"ready_for_match"=>
          [#<Student:0x007f891d0b4020
            id: 3444,
            status: "ready_for_match",
            tenant_id: 15>]},
       10=>
        {"in_partnership"=>
          [#<Student:0x007f8931d5ab58
            id: 25166,
            status: "in_partnership",
            tenant_id: 10>]}}}
    
        15
  •  0
  •   Vimal Bhatt    6 年前

    下面是Mehrdad Afshari的Java解决方案。

    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;
    
    public class Tree {
    
        Iterator<Node> buildTreeAndGetRoots(List<MyObject> actualObjects) {
            Map<Integer, Node> lookup = new HashMap<>();
            actualObjects.forEach(x -> lookup.put(x.id, new Node(x)));
            //foreach (var item in lookup.Values)
            lookup.values().forEach(item ->
                    {
                        Node proposedParent;
                        if (lookup.containsKey(item.associatedObject.parentId)) {
                            proposedParent = lookup.get(item.associatedObject.parentId);
                            item.parent = proposedParent;
                            proposedParent.children.add(item);
                        }
                    }
            );
            //return lookup.values.Where(x =>x.Parent ==null);
            return lookup.values().stream().filter(x -> x.parent == null).iterator();
        }
    
    }
    
    class MyObject { // The actual object
        public int parentId;
        public int id;
    }
    
    class Node {
        public List<Node> children = new ArrayList<Node>();
        public Node parent;
        public MyObject associatedObject;
    
        public Node(MyObject associatedObject) {
            this.associatedObject = associatedObject;
        }
    }