可以在JS中使用具有依赖关系的可遍历树的方法是什么?

时间:2015-08-07 12:05:39

标签: javascript jquery node.js algorithm tree

可能是自从我不确定如何表达它以来最糟糕的标题,但希望我能在这里解释得更好。请注意,这个问题中的错误术语比比皆是,我为此道歉。

我想尝试在节点中构建一个可以遍历依赖关系树的JS应用程序。通常使用jQuery遍历的普通树会很好,但我认为这比这更复杂。

我以此图片为例:

https://i.imgur.com/MQHWBDk.png(从以前的图片更新,因为在某些浏览器中它重定向到较小的分辨率)

我希望能够选择一个节点并让应用程序输出到该节点的最有效路由,包括所有依赖项。例如,如果我想进入屏蔽技术1,它将输出: 研究实验室1 - >研究实验室2 - >研究实验室3 - >研究实验室4 - >研究实验室5 - >研究实验室6 - >能源技术1 - >能源技术2 - >能源技术3 - >屏蔽技术1

在这个例子中,研究实验室是优先考虑的事项,但只要它遵循两条路径,任何订单都可以。

到目前为止,我还没有真正了解如何处理它。如果它是一个没有多个依赖关系的简单树结构,我只需将其设置为树。

如果您知道如何操作,请随时提取较小的示例。

2 个答案:

答案 0 :(得分:1)

依赖结构不是树,它是directed acyclic graph或DAG:

  • 定向:边缘从一个节点到另一个节点,因此有方向;
  • 非循环:没有周期;
  • 图形:节点可以有多个传入边缘(与不同,它们最多只有一个父节点)。

(我之所以提到这一点,是因为DAG在各种应用程序中都非常棒且非常有用,包括这个。它值得你花时间去了解它们。)

您正在寻找的是{"目标"的depth-firstbreadth-first遍历。节点。 (在您的示例图像中,您将沿着边缘向后移动。)

你想要哪一个?这取决于你想要优先考虑的事项:深度优先将倾向于首先完成链(例如RL1 - > RL2 - > ... - > ET1 - > ET2 - > ...如您提议的& #34; route"),而广度优先将倾向于完成"等级"首先(例如RL1 - > ET1 - > RL2 - > ET2 - > ...)

答案 1 :(得分:1)

我绝对建议对candu提出的有向图进行一些研究,并找到一种方法来实现这一目标,让您感到满意。可能有很多不同的方法可以做到这一点,由你来决定首选的架构。

如果您只是搞乱有向图,我想出了我自己的“俱乐部级”解决方案,它可能没有采用最佳实践,并且在生产代码中使用时有点过于粗略(除非你知道)你在做什么)。可能会对我写的内容进行一些改进。 警告经理!

首先,我创建了一个数组来表示有向无环图(或“DAG”)中的每个单独节点,并为每个节点分配一个唯一的ID。 ID对应于其在nodes数组中的位置。

var nodes = [];

nodes.push(
    {
        Name: "Research Lab 1",
        Adjacencies: [],
        ID: null
    },
    {
        Name: "Research Lab 2",
        Adjacencies: [],
        ID: null
    },
    {
        Name: "Computer Technology 1",
        Adjacencies: [],
        ID: null
    },
    {
        Name: "Energy Technology 1",
        Adjacencies: [],
        ID: null
    },
    {
        Name: "Combustion Drive 1",
        Adjacencies: [],
        ID: null
    },
    {
        Name: "Energy Technology 2",
        Adjacencies: [],
        ID: null
    },
    {
        Name: "Computer Technology 8",
        Adjacencies: [],
        ID: null
    },
    {
        Name: "Impulse Drive 1",
        Adjacencies: [],
        ID: null
    },
    {
        Name: "Research Lab 3",
        Adjacencies: [],
        ID: null
    },
    {
        Name: "Armor Technology 1",
        Adjacencies: [],
        ID: null
    });

for (var i = 0; i < nodes.length; i++) {
    nodes[i]["ID"] = i;
}

<小时/> 枚举节点后,必须建立节点之间的父/子(或尾/头)关系。我使用术语“邻接”来指代两个节点之间的父/子关系。 1

首先,我创建了一个函数来填充节点对象的Adjacencies属性 - 最终将用于创建基于nodes数组的邻接矩阵。

Array.prototype.addAdjacency = function (tail, head) {
    var thisNode = findNode(tail);
    thisNode["Adjacencies"].push(findNode(head).Name);
}

// This will return the node object with a given name:
function findNode(name) {
    return $.grep(nodes, function (n) {
        return n.Name == name
    })[0];
}

最后,我们可以手动指定每个节点的尾部/头部关系。

nodes.addAdjacency("Research Lab 1", "Research Lab 2");
nodes.addAdjacency("Research Lab 1", "Computer Technology 1");
nodes.addAdjacency("Research Lab 1", "Energy Technology 1");
nodes.addAdjacency("Research Lab 2", "Impulse Drive 1");
nodes.addAdjacency("Research Lab 2", "Research Lab 3");
nodes.addAdjacency("Research Lab 2", "Armor Technology 1");
nodes.addAdjacency("Computer Technology 1", "Computer Technology 8");
nodes.addAdjacency("Computer Technology 1", "Impulse Drive 1");
nodes.addAdjacency("Energy Technology 1", "Combustion Drive 1");
nodes.addAdjacency("Energy Technology 1", "Energy Technology 2");
nodes.addAdjacency("Energy Technology 1", "Impulse Drive 1");

adjacency matrix是一个二维数组,显示DAG中每个节点之间的每个尾部/头部关系 - 这是一种额外的便利。在我的实施中,如果adjacencyMatrix[0][3] === true,则nodes[0]nodes[3]的父级(换句话说,“研究实验室1”是“能源技术1”的父级)。

function isAdjacent(tail, head) {
    return tail["Adjacencies"].indexOf(head.Name) != -1;
}

var adjacencyMatrix = populateAdjacencyMatrix(nodes);

function populateAdjacencyMatrix(vertices) {

    var matrix = new Array(vertices.length);

    for (var i = 0; i < vertices.length; i++) {
        matrix[i] = new Array(vertices.length);
    }

    for (var i = 0; i < matrix.length; i++) {
        for (var j = 0; j < matrix.length; j++) {
            if (isAdjacent(vertices[i], vertices[j])) {
                matrix[i][j] = true;
            } else {
                matrix[i][j] = false;
            }
        }
    }

    return matrix;
}

一旦邻接矩阵完成,我们就可以递归遍历矩阵,找到通向给定节点的每条路径。

var pathsToNode = [];

// ... e.g. the node at index 7, or "Impulse Drive 1":
findPathTo(nodes[7], []);

function findPathTo(node, path) {
    var nodeID = node["ID"];
    var parentNodeIndices = [];
    var currentNodeIsRootNode = true;

    // A new array based off of the path argument needs to be created, or else things get a little wacky with the scope.
    var currentPath = new Array();

    for (var i = 0; i < path.length; i++) {
        currentPath.push(path[i]);
    }

    // Next, add the node in this context to the 'current path'.
    currentPath.unshift(nodeID);

    // Note that if there are no paths leading up to this node, then it's the first node in the directed path.
    for (var i = 0; i < adjacencyMatrix[0].length; i++) {
        var matrixValue = adjacencyMatrix[i][nodeID];
        if (matrixValue === true) {
            currentNodeIsRootNode = false;
            parentNodeIndices.push(i);
        }
    }

    // Finally, if the node in /this/ recursive context is a "root" node (i.e. it has no parents),
    // then add the entire path to the list of paths to the original node.
    if (currentNodeIsRootNode) {
        //console.log(" ");
        //console.log("Adding path to paths array:")
        //console.log(currentPath);
        //console.log("This is now what the paths array looks like:")
        //console.log(pathsToNode);
        pathsToNode.push(currentPath);
    } else {
        // Otherwise, get all of the indices of the 'parent' nodes (relative to the node in this recursive context),
        // and recursively find the parent nodes of each.

        //console.log(" ");
        //console.log("Recursing findPathTo function.  Next nodes:")
        //console.log(nextNodeIndices);
        //console.log("Current path:")
        //console.log(currentPath);
        for (var i = 0; i < parentNodeIndices.length; i++) {
            findPathTo(nodes[parentNodeIndices[i]], currentPath);
        }
    }
}

console.log(pathsToNode);

最后,给定nodes[7],我们有一个名为pathsToNode的数组数组,其中填充了从“根”节点到给定节点的有序节点路径。像[1, 3, 5]这样的路径意味着nodes[1]nodes[3]的父级,nodes[3]nodes[5]的父级。

希望这会有所帮助,欢呼! :)

1 这可能是该术语的错误用法。