如何在JavaScript中实现Dijkstra的算法

我一直在阅读Grokking Algorithms ,我向所有算法新手都推荐它。 基本上是我几个月前希望做的介绍! 本书中的示例都是用Python编写的,因此我想分享Dijkstra算法JavaScript版本。 该算法使用有向加权图来确定到达节点的“最便宜”路径。

我将把每个部分分为几个步骤,并提供一些背景信息。 如果您只想看代码,这里是要点的链接

背景:什么是图?

图是用于对一组连接进行建模的抽象数据类型。 例如,假设鲍勃在Twitter上关注莎拉和娄。 莎拉跟随林。 楼跟随林和马克。 我们可以使用图形来表示这些连接。

该图说明了一个非常基本的Twitter网络。

每个人都是一个节点 ,每个连接都是一个边缘。 每个节点可以连接到许多其他节点。 可以对图形进行定向,以使每个连接都是单向的,例如在此Twitter示例中— Bob跟随Sarah,但是Sarah不跟随Bob。 图也可以是无向的,以便连接双向运行。

图形中的边也可以加权。 例如,在下图中,每个权重代表从点到点获得的成本。

挑战

假设您正在尝试找出到达目的地的最便宜的方法,如下图所示。 “起点”和“终点”之间的节点是您可以走的桥梁和道路,权重指的是您必须支付的通行费/汽油费。

我们正在努力解决的问题

乍一看,看起来事情可能会变得多毛! 有许多不同的可能途径可以评估和比较。 这个图相对简单-如果我们遇到更多节点和可能路径的更复杂问题,该怎么办?

但是Dijkstra的算法解决了这个令人生畏的问题,并通过一些简单的步骤将其分解为最终解决方案。 Dijkstra的算法也非常适合此特定用例。 用于表示可能路径的图形是有向的和非循环的(意味着没有循环)。

该方法

实施图

让我们弄清楚如何在程序中实现图形。 我将使用嵌套的JavaScript对象:

const graph = {
start: {A: 5, B: 2},
A: {C: 4, D: 2},
B: {A: 8, D: 7},
C: {D: 6, finish: 3},
D: {finish: 1},
finish: {}
};

每个节点由图形对象中的键表示。 每个密钥都有一个值对象,代表直接邻居和到达该邻居的成本。

例如,节点A连接到节点CD。 节点A是节点CD的“父级” 它们是节点A的“子级”

了解算法

现在,让我们概述Dijkstra算法的主要步骤。

  1. 找到“最便宜”的节点。
  2. 更新此节点的直接邻居的成本。
  3. 重复步骤1和2,直到对每个节点都完成了。
  4. 返回到达该节点的最低成本,以及到达该节点的最佳路径。

所以,如果我们是在创业之初,我们有前两个节点其中有5成本,和B具有2 B的成本是最便宜的节点。 这些是到目前为止除终点以外我们唯一知道的节点 而且,由于我们尚不知道完成成本,因此将其设置为Infinity。

要跟踪的东西已经很多了,我们只是从第一个节点开始! 我们为什么不使用新的数据结构来跟踪到达每个节点的最低成本?

我们将使用一个对象来对此进行跟踪。 到目前为止,它看起来像这样:

const costs = {
A: 5,
B: 2,
finish: Infinity
};

但是我们不只是想知道到达终点的成本。 我们想知道到达那里需要走的路! 这需要使用另一个数据结构来跟踪每个节点的父节点。 当一个节点有许多可能的父节点时,我们将仅保留导致成本最低的父节点。

这就是我们追溯从头到尾花费的最便宜路径的方式。

const parents = {
A: 'start',
B: 'start',
finish: null
};

现在我们还不知道到达终点的完整路径,因为我们没有终点的父节点

我们也不想浪费时间反复遍历相同的节点。 我们想要跟踪哪些节点已经被“处理”。 “已处理”表示我们已经计算了到达每个节点子节点的成本。

为此,我们可以简单地使用数组。 处理完节点后,我们会将其推入阵列。

const processed = [“start”, “A”, “B”];

好的,这是我们再次使用的图形。

我们希望继续找到最便宜的节点,并更新该节点子节点的成本。 最便宜的节点是B,其子节点是A(成本为8)和D(成本为7)。 我们将这些添加到成本对象中,现在看起来像:

console.log(costs)
// returns something like
{ A: 5,
B: 2,
D: 9
finish: Infinity
};

我们不更新A的成本,因为5比8便宜。我们将D的值加上9,因为到达B的成本是2,从B到达D的成本是7,所以7 + 2 = 9。

我们还将更新处理后的数据和父级数据结构。 我们将重复上述步骤,直到处理完所有节点为止。

实现算法

如果尚不清楚,请放心,我们将逐步进入代码。

首先,我们将定义一个函数,该函数将给出成本和已处理的节点,将返回尚未处理的最便宜的节点。

const lowestCostNode = (costs, processed) => {
return Object.keys(costs).reduce((lowest, node) => {
if (lowest === null || costs[node] < costs[lowest]) {
if (!processed.includes(node)) {
lowest = node;
}
}
return lowest;
}, null);
};

然后,我们定义主要函数dijkstra ,它将初始图作为参数。 我们将从创建costparent和已处理数据结构开始。

const dijkstra = (graph) => {
  const costs = Object.assign({finish: Infinity}, graph.start);
  const parents = {finish: null};
  for (let child in graph.start) {  // add children of start node
parents[child] = 'start';
}
  const processed = [];
...

接下来,我们将使用lowestCostNode函数设置要处理的节点的初始值。 然后,我们将开始一个while循环,它将不断寻找最便宜的节点。

  let node = lowestCostNode(costs, processed);
while (node) {
    let cost = costs[node];
    let children = graph[node];
    for (let n in children) {
let newCost = cost + children[n];
if (!costs[n]) {
costs[n] = newCost;
parents[n] = node;
}
if (costs[n] > newCost) {
costs[n] = newCost;
parents[n] = node;
}
}
    processed.push(node);
    node = lowestCostNode(costs, processed);
  }

这是上面发生的事情的详细描述:

  1. 获取当前节点的成本
  2. 获取当前节点的所有邻居(也称为“子代”)。
  3. 遍历每个子节点,并计算到达该子节点的成本。 我们将在costs对象中更新该节点的cost 如果最便宜或只有可用成本。
  4. 我们还将更新父级数据结构,以便最终可以追溯到我们的步骤。
  5. 节点完全处理后,我们将其推送到处理后的数据结构中。
  6. 将当前节点的值重置为最便宜的未处理节点,然后重复执行。

最后,一旦while循环完成,我们将以最低的成本到达终点节点。 现在,我们要获取到该节点的路径,这可以通过使用parents对象来跟踪步骤来完成。

  let optimalPath = ['finish'];
  let parent = parents.finish;
  while (parent) {
optimalPath.push(parent);
parent = parents[parent];
}

optimalPath.reverse(); // reverse array to get correct order
  const results = {
distance: costs.finish,
path: optimalPath
};
  return results;
}; //end of function 

我们的最终结果应如下所示!

{ distance: 8, path: [ 'start', 'A', 'D', 'finish' ] }

要查看完整的解决方案,请检查要点。

如果仍然遇到问题,建议您在计算机上运行代码并更改程序的各个部分。 我发现调整和玩弄代码确实有助于我对正在发生的事情有更深入的了解。

我希望你喜欢这个解释。 感谢您抽出宝贵的时间阅读本文!

**更新:为更加清晰起见,我重新访问了原始解决方案,并结合了反馈和更清晰的变量名以及一些日志记录,以帮助您了解正在发生的事情。 你可以在这里找到它。

— —
如果您喜欢这首作品,请打绿色的心heart,这样其他人也可能会偶然发现它! 也可以在 Github Twitter 上随时关注我

From: https://hackernoon.com/how-to-implement-dijkstras-algorithm-in-javascript-abdfd1702d04

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值