从节点和链接列表构建一棵树

时间:2021-07-01 11:23:43

标签: javascript algorithm tree

我有一个图形编辑器,可以绘制 d3 力有向图(具有多项选择和结果的问卷,例如,如果您对问题 1 回答是,则转到问题 2,否则转到问题 1b)。< /p>

它有一个根节点,节点可以有多个父节点,多个子节点。

在内部,它表示为一个节点列表和一个链接列表。

示例输入

const nodes = [
    {id:1, data: {display: "start"}},
    {id:2, data: {display: "question 1"}},
    {id:3, data: {display: "question 2"}},
    {id:4, data: {display: "question 1b"}},
    {id:5, data: {display: "End"}},
];
   
const links = [
    {source:1,target:2},
    {source:2,target:3},
    {source:2,target:4},
    {source:4,target:3},
    {source:3,target:5},
];

预期输出

我需要生成一个显示所有可能路径的树,如下所示:

{
    Id:1
    Data: {display: "start"},
    Children: [
        {
            Id:2,
            Data: {display: "question 1"},
            Children:[
                {
                    Id:3,
                    Data: {display: "question 2"},
                    Children:[
                        {
                            Id:5
                            Data: "End",
                            Children:[]
                        }
                    ]
                },
                {
                    Id:4,
                    Data: {display: "question 1 b"},
                    Children:[
                        {
                            Id:3,
                            Data: {display: "question 2"},
                            Children:[
                                {
                                    Id:5
                                    Data: "End",
                                    Children:[]
                                }
                            ]
                        }
                    ]
                }
            ]
        }
    ]
}

尝试

到目前为止,这是我(非常幼稚)的尝试:

const nodes = [
    {id:1, data: {display: "start"}},
    {id:2, data: {display: "question 1"}},
    {id:3, data: {display: "question 2"}},
    {id:4, data: {display: "question 1b"}},
    {id:5, data: {display: "End"}},
];
   
const links = [
    {source:1,target:2},
    {source:2,target:3},
    {source:2,target:4},
    {source:4,target:3},
    {source:3,target:5},
]

const startNode = nodes.find(n => n.id == 1);

const jsonTree = {
    id: startNode.id,
    data: startNode.data,
    children: [],
}

const nodeList = [];
nodeList.push(startNode);

while (nodeList.length > 0) {
    const currentNode = nodeList.shift();
    if (!currentNode) continue;
    currentNode.visited = true;
    const childNodes = this.findChildrenOfNode(currentNode, nodes, links);
    const position = this.findPositionInJson(jsonTree, currentNode);

    position.data = currentNode.data;
    position.id = currentNode.id
    childNodes.forEach((c) => {
    if (!c.visited) {
        c.visited = true;
        nodeList.push(c);
    }
    const child = {
        id:c.id,
        data: c.data,
        children: [],
    };

    position.children.push(child);
    });
}

function findChildrenOfNode(node, nodes, links) {
    const childLinksTargetIds = [];

    links.forEach((l) => {
      if (node.id === l.source) childLinksTargetIds.push(l.target);
    });
    return nodes.filter((n) => childLinksTargetIds.includes(n.id));
 }

function findPositionInJson(jsonObject, node) {
    if (!jsonObject) return null;
    if (jsonObject.hasOwnProperty('data')) {
      if (jsonObject.id === node.id) return jsonObject;
    }

    const keys = Object.keys(jsonObject);
    for (let i = 0; i < keys.length; i += 1) {
      const key = jsonObject[keys[i]];

      if (typeof key === 'object') {
        const o = this.findPositionInJson(key, node);
        if (o != null) return o;
      }
    }
    return null;
  }

console.log(jsonTree)

问题

我的代码将填充“1b”的直接孩子,但不会递归(“1b”的孩子是“2”,其孩子应该是“end”)

我想有一种算法可以准确地做我想做的事情。怎么办?

2 个答案:

答案 0 :(得分:1)

首先创建一个映射,用于将 id 值映射到相应的对象。为每个对象添加一个空的 children 数组属性。然后从 children 信息填充 links 数组,每次在地图中查找 sourcetarget 标识符。最后返回 id 1 的地图条目:

function buildTree(nodes, links) {
    let map = new Map(nodes.map(o => [o.id, {...o, children: []}]));
    for (let {source, target} of links) map.get(source).children.push(map.get(target));
    return map.get(1);
}

// Demo
const nodes = [
    {id:1, data: {display: "start"}},
    {id:2, data: {display: "question 1"}},
    {id:3, data: {display: "question 2"}},
    {id:4, data: {display: "question 1b"}},
    {id:5, data: {display: "End"}},
];
   
const links = [
    {source:1,target:2},
    {source:2,target:3},
    {source:2,target:4},
    {source:4,target:3},
    {source:3,target:5},
]

console.log(buildTree(nodes, links));

答案 1 :(得分:1)

您可以使用递归来缩短解决方案:

const nodes = [
   {id:1, data: {display: "start"}},
   {id:2, data: {display: "question 1"}},
   {id:3, data: {display: "question 2"}},
   {id:4, data: {display: "question 1b"}},
   {id:5, data: {display: "End"}},
];

const links = [
   {source:1,target:2},
   {source:2,target:3},
   {source:2,target:4},
   {source:4,target:3},
   {source:3,target:5},
];
var nds = Object.fromEntries(nodes.map(x => [x.id, x.data]))
function to_tree(n = 1){
   return {Id:n, data:nds[n], children:links.filter(x => x.source === n).map(({source:_, target:t}) => to_tree(t))}
}
console.log(to_tree())