繁体   English   中英

来自邻接表的树结构

[英]Tree Structure from Adjacency List

我正在尝试从具有父 ID 的平面数组生成分层树对象。

// `parent` represents an ID and not the nesting level.
var flat = [
    { id: 1, name: "Business", parent: 0 },
    { id: 2, name: "Management", parent: 1 },
    { id: 3, name: "Leadership", parent: 2 },
    { id: 4, name: "Finance", parent: 1 },
    { id: 5, name: "Fiction", parent: 0 },
    { id: 6, name: "Accounting", parent: 1 },
    { id: 7, name: "Project Management", parent: 2  }
];

最终的树对象应如下所示:

{ 
    id: 1, 
    name: "Business", 
    children: [
        { 
            id: 2, 
            name: "Management", 
            children: [
                { id: 3, name: "Leadership" },
                { id: 7, name: "Project Management" }
            ]
        }
        // [...]
    ]
}
// [...]

你可以看到我目前在这个 fiddle上的工作,但它只适用于前两个级别。

我想过收集孤儿(从没有父母在tree flat对象)然后再次循环查看它们是否现在有父母。 但这可能意味着在树对象上有许多循环,尤其是在多个级别上有许多类别。

我相信有一个更优雅的解决方案。

看起来无论树的深度如何,您都可以通过 2 次执行此操作:

var flat = [
    { id: 1, name: "Business", parent: 0 },
    { id: 2, name: "Management", parent: 1 },
    { id: 3, name: "Leadership", parent: 2 },
    { id: 4, name: "Finance", parent: 1 },
    { id: 5, name: "Fiction", parent: 0 },
    { id: 6, name: "Accounting", parent: 1 },
    { id: 7, name: "Project Management", parent: 2  }
];

var nodes = [];
var toplevelNodes = [];
var lookupList = {};

for (var i = 0; i < flat.length; i++) {
    var n = {
        id: flat[i].id,
        name: flat[i].name,
        parent_id: ((flat[i].parent == 0) ? null : flat[i].parent),
        children: []
        };
    lookupList[n.id] = n;
    nodes.push(n);
    if (n.parent_id == null) {
        toplevelNodes.push(n);
    }
}

for (var i = 0; i < nodes.length; i++) {
  var n = nodes[i];
  if (!(n.parent_id == null)) {
      lookupList[n.parent_id].children = lookupList[n.parent_id].children.concat([n]);
  }
}

console.log(toplevelNodes);

更新 2,(六年后)

我碰巧再次遇到了这个问题,并意识到通过一点 ES6 解构和简单的递归,这可以变得多么简单:

 const makeForest = (id, xs) => xs .filter (({parent}) => parent == id) .map (({id, parent, ...rest}) => ({id, ...rest, children: makeForest (id, xs)})) var flat = [{id: 1, name: "Business", parent: 0}, {id: 2, name: "Management", parent: 1}, {id: 3, name: "Leadership", parent: 2}, {id: 4, name: "Finance", parent: 1}, {id: 5, name: "Fiction", parent: 0}, {id: 6, name: "Accounting", parent: 1}, {id: 7, name: "Project Management", parent: 2}]; console .log ( makeForest (0, flat) )
 .as-console-wrapper {min-height: 100% !important; top: 0}

注意名称的变化。 我们不是在制作一棵树,而是整个森林,根的每个直接子节点都有自己的节点。 如果您有一个用于根的结构,那么更改它以生成一棵树会相对简单。 还要注意,这会让我们找到以任何id 为根的树。 它不必是整体根。

表现

正如您所担心的,这确实具有O(n^2)的最坏情况性能。 实际运行时间类似于O(n * p) ,其中 p 是列表中不同父节点的数量。 因此,当我们有一棵树几乎和列表的长度一样深时,我们只会处理最坏的情况。 我会使用这样的东西,除非并且直到我可以证明它是我的代码中的热点。 我的猜测是我永远不会发现需要更换它。

永远不要计算递归。 这比这里的任何其他答案都要简单得多,包括我下面的两个版本。


更新 1

我对原始解决方案的额外复杂性不满意。 我正在添加另一个降低复杂性的版本。 它设法在一次通过中构建数据。 如有必要,它还使人们有机会以不同于原始格式的方式重组树中的记录。 (默认情况下,它只删除parent节点。)

更新后的版本

JSFiddle可用

var makeTree = (function() {
    var defaultClone = function(record) {
        var newRecord = JSON.parse(JSON.stringify(record));
        delete newRecord.parent;
        return newRecord;
    };
    return function(flat, clone) {
        return flat.reduce(function(data, record) {
            var oldRecord = data.catalog[record.id];
            var newRecord = (clone || defaultClone)(record);
            if (oldRecord && oldRecord.children) {
                newRecord.children = oldRecord.children;
            }
            data.catalog[record.id] = newRecord;
            if (record.parent) {
                var parent = data.catalog[record.parent] = 
                        (data.catalog[record.parent] || {id: record.parent});
                (parent.children = parent.children || []).push(newRecord);
            } else {
                data.tree.push(newRecord);
            }
            return data;
        }, {catalog: {}, tree: []}).tree;
    }
}());

请注意,无论平面列表的顺序如何,这都将起作用——父节点不必在它们的子节点之前——尽管这里没有任何东西可以对节点进行排序。


原版

我的解决方案(也在JSFiddle 上):

var makeTree = (function() {
    var isArray = function(obj) {return Object.prototype.toString.call(obj) == "[object Array]"; };
    var clone = function(obj) {return JSON.parse(JSON.stringify(obj));};
    var buildTree = function(catalog, structure, start) {
        return (structure[start] || []).map(function(id, index) {
            var record = catalog[id];
            var keys = structure[start][index];
            var children = isArray(keys) ? keys.map(function(key) {
                return buildTree(catalog, structure, key);
            }) : buildTree(catalog, structure, keys);
            if (children.length) {
                record.children = children;
            }
            return record;
        })
    };
    return function(flat) {
        var catalog = flat.reduce(function(catalog, record) {
            catalog[record.id] = clone(record);
            delete(catalog[record.id].parent);
            return catalog;
        }, {});
        var structure = flat.reduce(function(tree, record) {
            (tree[record.parent] = tree[record.parent] || []).push(record.id);
            return tree;
        }, {});
        return buildTree(catalog, structure, 0); // this might be oversimplified.
    }
}());

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM