如何从平面结构有效地建造树木?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何从平面结构有效地建造树木?相关的知识,希望对你有一定的参考价值。

我有一堆扁平结构的物体。这些物体有IDParentID属性,因此它们可以安排在树上。它们没有特别的顺序。每个ParentID属性不一定与结构中的ID匹配。因此它们可能是从这些物体中出现的几棵树。

您将如何处理这些对象以创建生成的树?

我不是一个解决方案,但我确信它远非最佳...

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

没有循环引用。当ParentID == null或在其他对象中找不到ParentID时,Node是RootNode

答案

将对象的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);
}
另一答案

这是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;
    }
}
另一答案

你是否只使用这些属性?如果没有,那么创建子节点数组可能会很好,您可以在其中循环遍历所有这些对象以构建此类属性。从那里,选择具有子节点但没有父节点的节点,并从上到下迭代地构建您的树。

另一答案

我可以在4行代码和O(n log n)时间内完成此操作,假设Dictionary类似于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.
另一答案

大多数答案都假设您希望在数据库之外执行此操作。如果您的树本质上是相对静态的,并且您只需要以某种方式将树映射到数据库中,您可能需要考虑在数据库端使用嵌套集表示。查看Joe Celko的书籍(或者here获得Celko的概述)。 如果仍然绑定到Oracle dbs,请查看他们的CONNECT BY以获得直接的SQL方法。 无论使用哪种方法,您都可以在将数据加载到数据库之前完全跳过映射树。只是想我会提供这个替代方案,它可能完全不适合您的特定需求。原始问题的整个“正确顺序”部分在某种程度上意味着您需要在数据库中由于某种原因使命令“正确”?这可能会促使我在那里处理树木。

另一答案

这与提问者所寻求的并不完全相同,但我很难绕过这里提供的含糊不清的答案,我仍然认为这个答案符合标题。

我的答案是将平面结构映射到直接在对象树上,你所拥有的只是每个对象上的ParentIDParentIDnull0,如果它是根。在提问者的对面,我假设所有有效的ParentID指向列表中的其他内容:

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;
另一答案

这是一个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>]}}}
另一答案

接受的答案对我来说太复杂了,所以我要添加它的Ruby和NodeJS版本

假设平面节点列表具有以下结构:

nodes = [
  { id: 7, parent_id: 1 },
  ...
] # ruby

nodes = [
  { id: 7, parentId: 1 },
  ...
] # nodeJS

将上面的平面列表结构转换为树的功能看起来如下

对于Ruby:

def to_tree(nodes)

  nodes.each do |node|

    parent = nodes.find { |another| another[:id] == node[:parent_id] }
    next unless parent

    node[:parent] = parent
    parent[:children] ||= []
    parent[:children] << node

  end

  nodes.select { |node| node[:parent].nil? }

end

对于NodeJS:

const toTree = (nodes) => {

  nodes.forEach((node) => {

    const parent = nodes.find((another) => another.id == node.parentId)
    if(parent == null) return;

    node.parent = parent;
    parent.children = parent.children || [];
    parent.children = parent.children.concat(node);

  });

  return nodes.filter((node) => node.pare

以上是关于如何从平面结构有效地建造树木?的主要内容,如果未能解决你的问题,请参考以下文章

学习设计模式之建造者模式

如何有效地检索树中节点的路径(与帖子“将平面表解析为树?”相关)

如何有效地链接来自 Kafka Streams 中平面 api 数据的 groupby 查询?

HERE-API识别建筑物的地板/地平面

如何有效地打开 30gb 的文件并处理其中的片段而不减慢速度?

在八叉树中合并叶子