可能是自从我不确定如何表达它以来最糟糕的标题,但希望我能在这里解释得更好。请注意,这个问题中的错误术语比比皆是,我为此道歉。
我想尝试在节点中构建一个可以遍历依赖关系树的JS应用程序。通常使用jQuery遍历的普通树会很好,但我认为这比这更复杂。
我以此图片为例:
https://i.imgur.com/MQHWBDk.png(从以前的图片更新,因为在某些浏览器中它重定向到较小的分辨率)
我希望能够选择一个节点并让应用程序输出到该节点的最有效路由,包括所有依赖项。例如,如果我想进入屏蔽技术1,它将输出: 研究实验室1 - >研究实验室2 - >研究实验室3 - >研究实验室4 - >研究实验室5 - >研究实验室6 - >能源技术1 - >能源技术2 - >能源技术3 - >屏蔽技术1
在这个例子中,研究实验室是优先考虑的事项,但只要它遵循两条路径,任何订单都可以。
到目前为止,我还没有真正了解如何处理它。如果它是一个没有多个依赖关系的简单树结构,我只需将其设置为树。
如果您知道如何操作,请随时提取较小的示例。
答案 0 :(得分:1)
依赖结构不是树,它是directed acyclic graph或DAG:
(我之所以提到这一点,是因为DAG在各种应用程序中都非常棒且非常有用,包括这个。它值得你花时间去了解它们。)
您正在寻找的是{"目标"的depth-first或breadth-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 这可能是该术语的错误用法。