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

时间:2009-01-14 19:14:09

标签: algorithm tree language-agnostic

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

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

我离解决方案不远,但我确定它远非最佳......

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

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

19 个答案:

答案 0 :(得分:101)

将对象的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);
}

答案 1 :(得分:29)

根据Mehrdad Afshari的答案以及Andrew Hanlon对加速的评论,这是我的看法。

与原始任务的重要区别:根节点具有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;
}

答案 2 :(得分:26)

这是一个简单的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);

答案 3 :(得分: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": {}
            }
        }
    }
}

答案 4 :(得分:6)

JS版本,返回一个根或一个根数组,每个根都有一个包含相关子节点的Children数组属性。不依赖于有序输入,体面紧凑,不使用递归。请享用!

use List::Util qw(any);

答案 5 :(得分:2)

在此处找到了一个非常棒的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);

答案 6 :(得分:1)

在我看来这个问题很模糊,我可能会创建一个从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),您也可以获得父级&gt;子地图。

答案 7 :(得分:1)

这是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;
    }
}

答案 8 :(得分:1)

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

请注意,只有当 table parent_id 排序时,此解决方案才有效。

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>();
}

答案 9 :(得分:1)

我在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;
  }
}

答案 10 :(得分:0)

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

我的回答是将平面结构映射到直接在对象树上,其中您拥有的每个对象都是ParentID。如果ParentID是根,则null0ParentID。在提问者的对面,我假设所有有效的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; 都指向列表中的其他内容:

export TOKEN='eyJqdGkiOiIzZDkzNz'
curl https://apis.xxxxxxxx.com/v2-beta/media \
    --header "Authorization: Bearer $TOKEN" \
    --form 'configuration={"configuration" : {"executor": "v2"}}' \
    --form media="@recording.mp3" \
    | tee media-post-response.json | jq '.'
export MEDIA_ID=$( jq --raw-output '.mediaId' < media-post-response.json )
echo "Uploaded file with mediaId = ${MEDIA_ID}"

答案 11 :(得分:0)

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

无论如何,如果绑定到Oracle dbs,请查看他们的CONNECT BY以获得直接的SQL方法。

使用这两种方法,您可以在将数据加载到数据库之前完全跳过映射树。只是想我会提供这个替代方案,它可能完全不适合您的特定需求。原始问题的整个“正确顺序”部分在某种程度上意味着您需要在数据库中由于某种原因使命令“正确”?这可能会促使我在那里处理树木。

答案 12 :(得分:0)

我可以在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.

答案 13 :(得分:0)

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

答案 14 :(得分:0)

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

答案 15 :(得分:0)

接受的答案对我来说似乎太复杂了,因此我要为其添加Ruby和NodeJS版本

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

core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
core.ignorecase=true
core.precomposeunicode=true
remote.origin.url=git@github.com:yort-xxx/xxx.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.master.remote=origin
branch.master.merge=refs/heads/master
branch.feature/troy/backend.remote=origin
branch.feature/troy/backend.merge=refs/heads/feature/troy/backend
branch.feature/troy/backend_reflection.remote=origin
branch.feature/troy/backend_reflection.merge=refs/heads/feature/troy/backend_reflection
user.name=yort
user.email=yort.xxx@gmail.com
credential.helper=

将上面的平面列表结构变成一棵树的功能看起来是这样的

对于Ruby:

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

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

对于NodeJS:

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

答案 16 :(得分:0)

执行此操作的一种优雅方法是将列表中的项目表示为字符串,其中包含点分隔的父母列表,最后是一个值:

server.port=90
server.hostname=localhost
client.serverport=90
client.database.port=1234
client.database.host=localhost

组装一棵树时,您最终会得到类似的东西:

server:
  port: 90
  hostname: localhost
client:
  serverport=1234
  database:
    port: 1234
    host: localhost

我有一个configuration library,它从命令行参数(列表)实现此替代配置(树)。 将列表中的单个项目添加到树is here的算法。

答案 17 :(得分:0)

java版本

// node
@Data
public class Node {
    private Long id;
    private Long parentId;
    private String name;
    private List<Node> children = new ArrayList<>();
}

// flat list to tree
List<Node> nodes = new ArrayList();// load nodes from db or network
Map<Long, Node> nodeMap = new HashMap();
nodes.forEach(node -> {
  if (!nodeMap.containsKey(node.getId)) nodeMap.put(node.getId, node);
  if (nodeMap.containsKey(node.getParentId)) {
    Node parent = nodeMap.get(node.getParentId);
    node.setParentId(parent.getId());
    parent.getChildren().add(node);
  }
});

// tree node
List<Node> treeNode = nodeMap .values().stream().filter(n -> n.getParentId() == null).collect(Collectors.toList());

答案 18 :(得分:0)

请参阅下面带有简单测试程序的完整 Java 8+ 解决方案。

<块引用>

这是对@"Vimal Bhatt" 版本进行修改的解决方案,该版本接受多个根

package tree;

import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Stream;

public class Tree {
    
    private <T> void swap(T[] input, int a, int b) {
        T tmp = input[a];
        input[a] = input[b];
        input[b] = tmp;
    }
    
    public static void main(String[] args) {
        Random r = new Random(8);

        MyObject[] myObjects = new MyObject[]{
                new MyObject(6, 3),
                new MyObject(7, 5),
                new MyObject(8, 0),
                new MyObject(1, 0),
                new MyObject(15, 12),
                new MyObject(12, 0),
                new MyObject(3, 5),
                new MyObject(4, 3),
                new MyObject(5, 2),
                new MyObject(2, 1),
                new MyObject(21, 8),
                new MyObject(9, 1)
        };
        
        Tree t = new Tree();
        // cinco trocas arbitrarias de posição
        for (int i = 0; i < 5; i++) {
            int a = r.nextInt(7) + 1;
            int b = r.nextInt(7) + 1;
            t.swap(myObjects, a, b);
        }
        System.out.println("The list have " + myObjects.length + " objects");
        for (MyObject o: myObjects) {
            System.out.print(" " + o);
        }
        Iterator<Node> iterator = t.buildTreeAndGetRoots(Arrays.asList(myObjects));
        int counter = 0;
        System.out.println("");
        while (iterator.hasNext()) {
            Node obj = iterator.next();
            System.out.println(++counter + "\t" + obj.associatedObject.id + "\t-> " + obj.associatedObject.parentId);
        }
    }

    Iterator<Node> buildTreeAndGetRoots(List<MyObject> actualObjects) {
        Node root = null;
        Map<Integer, Node> lookup = new HashMap<>();
        actualObjects.forEach(x -> lookup.put(x.id, new Node(x)));

        Stream<Node> roots = actualObjects.stream()
                .filter(x -> x.parentId == 0)
                .map(x -> new Node(x));
        Consumer<Node> nodeConsumer = item -> {
            Node proposedParent;
            if (lookup.containsKey(item.associatedObject.parentId)) {
                proposedParent = lookup.get(item.associatedObject.parentId);
                item.parent = proposedParent;
                proposedParent.children.add(item);
            }
        };
        lookup.values().forEach(nodeConsumer);
        Stream<Node> s2 = lookup.values().stream().filter(e -> e.associatedObject.parentId != 0);
        return Stream.concat(roots, s2).iterator();
    }
}

class MyObject { // The actual object
    public int parentId;
    public int id;

    MyObject(int id, int parent) {
        this.parentId = parent;
        this.id = id;
    }

    @Override
    public String toString() {
        return "{ " +
                "parent: " + parentId +
                ", id: " + id +
                " }" ;
    }
}

class Node {
    public List<Node> children = new ArrayList<Node>();
    public Node parent;
    public MyObject associatedObject;

    public Node(MyObject associatedObject) {
        this.associatedObject = associatedObject;
    }
}