如何遍历节点有多个父节点并总结值的图形?

时间:2016-08-01 08:28:20

标签: javascript algorithm recursion tree

我从JSON导出中收到这些数据:

nodes = [
    {id:1,data:29,parentOf:[]},
    {id:2,data:31,parentOf:[1,8,7]},
    {id:3,data:41,parentOf:[2,1]},
    {id:4,data:89,parentOf:[3,2,1]},
    {id:5,data:71,parentOf:[4,3,2,1,9,2,8,7]},
    {id:6,data:11,parentOf:[5,4,3,2,1]},
    {id:7,data:59,parentOf:[]},
    {id:8,data:43,parentOf:[7]},
    {id:9,data:97,parentOf:[2,8,7]}
]

这是一个图表中的数据结构,其中一个节点可以有零个或多个父节点。该图已展平,以便导出到节点数组中。 现在,我需要汇总数据字段的值。

如何遍历此图表以获取每个节点的数据总和?

编辑:所提供示例的最终结果如下:

[
    {id:1,sum:29},
    {id:2,sum:162}, // 31+102+29
    {id:3,sum:203}, // 41+162
    {id:4,sum:292}, // 89+203
    {id:5,sum:622}, // 71+292+259
    {id:6,sum:633}, // 11+622
    {id:7,sum:59},
    {id:8,sum:102}, // 43+59
    {id:9,sum:259}  // 97+162
]

编辑2:这是由上面的数据结构得出的图形图,由我做出:

enter image description here

我现在看到,在节点的parentOf数组中,有一些冗余信息。

尽管所提供的示例仅包含没有父节点的节点(id:6),但是我正在搜索解决方案以处理没有父节点的节点多于一个的正常情况。所以我认为图遍历应该从叶节点开始,即id:1和id:7。

非常感谢非递归方法(如果可能的话)。

2 个答案:

答案 0 :(得分:2)

使这个问题变得困难的是,parentOf列表有时不仅包含直接子项的 id 值,还包含(某些)更远程的后代。所以真正的图表并不直接明显。

例如,样本数据将节点9列为节点2,8和7的父节点。然而,图像显示其中只有2个是9的直接子节点,而8和7是更远的后代。节点1也是9的后代,未列在节点9的parentOf属性中。

其中的信息真正是直接的孩子可以从数据中推断出来,但它需要一些工作。然而,需要这些信息才能正确计算总和。

请注意,由于这种冗余输入,无法在图中定义三角形。假设节点A将节点B和C作为子节点,节点B将节点C作为子节点:这样的三角形在上述数据结构中不可编码,因为A和C之间的链接将被视为冗余"孙子"链接。

在我提到的解决方案中,我使用Set来记录后代和直接子节点,因为这样可以比数组更快地查找。

我还创建了一个Map,用于通过 id 访问节点。同样,这是出于性能原因。

当在图表中检测到循环时,将抛出异常。

根据要求,算法不依赖递归。

以下是代码:



function sumTree(nodes) {
    // Take copy of nodes array, so that the original is not mutated
    var nodes = nodes.map( node => ({
        id: node.id,
        childrenSet: new Set(node.parentOf), // convert to Set for fast look-up
        childrenSet2: new Set(node.parentOf), // copy 
        descendantSet: new Set(node.parentOf), // Will contain ALL descendants
        sum: node.data
    }) );
        
    // For faster lookup, create a Map with nodes keyed by their node.id.
    var hash = new Map(nodes.map( node => [node.id, node] ));

    // Create complete descendants set for each node
    nodesCopy = nodes.slice();
    while (true) {
        // Get index of first node that has no (more) children
        let i = nodesCopy.findIndex(node => !node.childrenSet.size);
        if (i < 0) break; // Not found: then we're done
        let id = nodesCopy.splice(i, 1)[0].id; // extract found node id
        nodesCopy.forEach(parent => {
            if (parent.childrenSet.has(id)) { // Found a parent of it
                // Extend the parent's descendant list with the node's one
                parent.descendantSet = new Set([...parent.descendantSet, 
                                                ...hash.get(id).descendantSet]);
                // Don't consider this node any more. 
                // At one point the parent may become a leaf
                parent.childrenSet.delete(id);
            }
        });
    }
    if (nodesCopy.length) throw "Exception: graph has cycles";

    // Create true children sets (only direct children, without "redundancy"):
    nodes.forEach( node => {
        node.childrenSet = new Set(node.childrenSet2);
        [...node.childrenSet]
            // Collect descendants of children
            .reduce( (remote, id) => 
                new Set([...remote, ...hash.get(id).descendantSet]), new Set() )
            // Remove any of those from the children's set
            //   (so no more "grand" children there)
            .forEach( id => node.childrenSet.delete(id) ); 
    });

    // Calculate the sums bottom-up
    nodesCopy = nodes.slice();
    while (true) {
        let i = nodesCopy.findIndex(node => !node.childrenSet.size);
        if (i < 0) break; 
        let leaf = nodesCopy.splice(i, 1)[0];
        nodesCopy.forEach(parent => {
            if (parent.childrenSet.has(leaf.id)) {
                parent.sum += leaf.sum;
                parent.childrenSet.delete(leaf.id);
            }
        });
    }

    // Only return the information we need
    return nodes.map( node => ({ id: node.id, sum: node.sum }) );
}

// Sample data
var nodes = [
    {id:1,data:29,parentOf:[]},
    {id:2,data:31,parentOf:[1,8,7]},
    {id:3,data:41,parentOf:[2,1]},
    {id:4,data:89,parentOf:[3,2,1]},
    {id:5,data:71,parentOf:[4,3,2,1,9,2,8,7]},
    {id:6,data:11,parentOf:[5,4,3,2,1]},
    {id:7,data:59,parentOf:[]},
    {id:8,data:43,parentOf:[7]},
    {id:9,data:97,parentOf:[2,8,7]}
];

// Calculate sums
var result = sumTree(nodes);

// Output result
console.log(result);
&#13;
&#13;
&#13;

答案 1 :(得分:0)

我想我错过了一些东西...... 请看下面的内容:

  1. 迭代所有节点
  2. 与所有node.data
  3. 求和parents.data
  4. 返回仅包含idsum属性的对象。
  5. &#13;
    &#13;
    var nodes = [
        {id:1,data:29,parentOf:[]},
        {id:2,data:31,parentOf:[1,8,7]},
        {id:3,data:41,parentOf:[2,1]},
        {id:4,data:89,parentOf:[3,2,1]},
        {id:5,data:71,parentOf:[4,3,2,1,9,2,8,7]},
        {id:6,data:11,parentOf:[5,4,3,2,1]},
        {id:7,data:59,parentOf:[]},
        {id:8,data:43,parentOf:[7]},
        {id:9,data:97,parentOf:[2,8,7]}
    ];
    
    
    var result = nodes
      .map((item, i, items) => {
        let r = Object.create(null); 
        
        r.id = item.id;
        
        r.sum = item.parentOf.reduce((prev, next, j) => {
          let parent = (items.filter(x => x.id === next)[0] || {});
          let parentData = parent.data || 0;
          
          return prev + parentData;
        }, item.data);
    
        return r;
      })
    ;
    
    console.log('result', result);
    &#13;
    &#13;
    &#13;