🥳每日一练-Dijkstra寻找最短路径-JS简易版

这篇文章分享 Dijkstra 算法的 JS 代码实现。超级简单清晰,快来看看吧

Dijkstra算法是一种用于寻找加权图中最短路径的算法。它从给定节点出发,逐步扩展其邻接节点,并更新最短路径。Dijkstra算法的核心思想是贪心算法,每次选择具有最短路径的邻接节点进行扩展

Dijkstra 算法和 prim 算法过程相同,都是会从下一个最小的边开始。不同的是 prim 算法会生成最小生成树,而 dijsktral 会得到从起始节点到各个节点的最短路径

下面开始

准备数据

这个图来自王道考研的数据结构视频课程中的截图。B 站可搜王道考研数据结构

首先准备一个有向图的邻接表数据,邻接表是按照上面图片生成的

javascript 复制代码
const nodeCount = 5; // 定义节点数量

/**@type {number[]} */
let graphNodes = Array(nodeCount) // 创建一个具有nodeCount个元素的数组,并用0填充
  .fill(0)
  .map((item, index) => index); // 将数组中的每个元素替换为它们的索引值

/**@type {number[][]} */
const graphEdges = { // 定义表示图的边的对象
  0: [ // 节点0的邻接节点和权重
    [1, 10],
    [4, 5],
  ],
  1: [ // 节点1的邻接节点和权重
    [2, 1],
    [4, 2],
  ],
  2: [[3, 4]], // 节点2的邻接节点和权重
  3: [ // 节点3的邻接节点和权重
    [0, 7],
    [2, 6],
  ],
  4: [ // 节点4的邻接节点和权重
    [1, 3],
    [2, 9],
    [3, 2],
  ],
};

/**
*
* @param {number[]} graphNodes
* @param {number[][]} graphEdges
* @returns {nodeType[]}
*/
const generateGraph = (graphNodes, graphEdges) => {
  // 将graphNodes数组中的每个元素转换为具有value和next属性的对象
  graphNodes = graphNodes.map((item, index) => {
    const tempNode = { value: index, next: null };
    return tempNode;
  });

  // 遍历graphEdges对象,并为每个节点设置next属性
  Object.entries(graphEdges).map(([value, edges]) => {
    graphNodes[value].next = edges.reduce((res, [toNode, power]) => {
      // 为每个邻接节点创建一个新的对象,并将其添加到结果对象的next属性中
      let tempRes = res;
      while (tempRes.next) tempRes = tempRes.next;
      tempRes.next = { value: toNode, power, next: null };
      return res;
    }, {}).next;
  });

  // 返回生成的图的节点数组
  return graphNodes;
};

const graph = generateGraph(graphNodes, graphEdges);

首先,代码定义了一个变量nodeCount,表示图中的节点数量。

然后,使用Array.from()方法创建一个具有nodeCount个元素的数组,并用0填充。接着,使用map()方法快速生成了一个节点的数组。表示节点是从 0 到 4

接下来,定义了一个表示图的边的对象graphEdges。这个对象将每个节点的邻接节点和权重存储在一个数组中。例如,graphEdges[0]表示节点0的邻接节点和权重,graphEdges[1]表示节点1的邻接节点和权重。

然后定义了一个用于生成图的函数generateGraph。该函数接受两个参数:graphNodes和graphEdges。graphNodes是一个表示节点值的数组,graphEdges是一个表示图的边的数组。函数的返回值是一个邻接表

generateGraph函数,首先遍历graphNodes数组,并为每个节点创建一个新的对象,其中包含value(节点值)和next(下一个节点)属性。然后,使用Object.entries()方法遍历graphEdges对象,并为每个节点设置next属性。最后,generateGraph函数返回生成的图的节点数组。

dijkstra

Dijkstra算法的步骤如下:

  1. 选择一个起点,将其标记为已访问,并将其距离设置为0。
  2. 遍历起点邻接的节点,计算与起点的距离,并更新最短路径。
  3. 重复步骤2,直到所有节点都被访问过为止。
  4. 返回终点的最短路径。

下面是代码实现

javascript 复制代码
const isVisited = Array(nodeCount).fill(false); // 定义一个记录节点是否已被访问过的数组
const lowCast = Array(nodeCount).fill(-1); // 定义一个记录节点到起点的最短路径的数组
const path = Array(nodeCount).fill(-1); // 定义一个记录路径上的前一个节点的数组

const startNode = 0; // 选择一个起点
isVisited[startNode] = true; // 将起点标记为已访问
lowCast[startNode] = 0; // 将起点到起点的距离设置为0
path[startNode] = -1; // 将起点路径的前一个节点设置为-1

const dijkstra = (graph, currentNode) => { // 定义一个Dijkstra算法函数
 // 更新路径
 for (let node = graph[currentNode].next; node !== null; node = node.next) { // 遍历当前节点的邻接节点
   if (!isVisited[node.value]) { // 如果节点未被访问过
     if (lowCast[node.value] == -1 || lowCast[node.value] > node.power + lowCast[currentNode]) { // 如果节点到起点的最短路径比当前记录的路径长
       lowCast[node.value] = node.power + lowCast[currentNode]; // 更新节点到起点的最短路径
       path[node.value] = currentNode; // 更新路径上的前一个节点
     }
   }
 }

 // 找到下一个未访问过的节点
 let minPower = Infinity; // 初始化最小路径
 let minIndex = -1; // 初始化最小节点的索引
 for (let i = 0; i < isVisited.length; i++) { // 遍历所有节点
   if (isVisited[i] == false && lowCast[i] < minPower && lowCast[i] !== -1) { // 如果节点未被访问过且路径比当前记录的路径短
     minPower = lowCast[i]; // 更新最小路径
     minIndex = i; // 更新最小节点的索引
   }
 }

 if (minIndex == -1) return; // 如果所有节点都已访问过,返回

 // 将找到的节点标记为已访问
 isVisited[minIndex] = -1; // 将找到的节点标记为已访问
 dijkstra(graph, minIndex); // 递归调用Dijkstra算法,以找到该节点的所有邻接节点
};

代码比较简单,下面简单描述一下过程:

首先,代码定义了三个数组:isVisited用于记录节点是否已被访问过,lowCast用于记录节点到起点的最短路径,path用于记录路径上的前一个节点。

接着,代码选择一个起点,将其标记为已访问,并将其距离设置为0。

然后,定义了一个名为dijkstra的函数,该函数接受两个参数:graph表示图的邻接表,currentNode表示当前节点。

在dijkstra函数中,首先更新路径。然后,找到下一个未访问过的节点,并检查其到起点的距离是否比当前记录的最短路径短。如果是,则更新最短路径和路径上的前一个节点。接着,找到一个未访问过的节点,并将其标记为已访问。然后,递归调用dijkstra函数,以找到该节点的所有邻接节点。

执行代码

javascript 复制代码
dijkstra(graph, startNode);

console.log(lowCast, path);
//[ 0, 8, 9, 7, 5 ] [ -1, 4, 1, 4, 0 ]

lowCast数组记录了每个节点到起点的最短路径,所以lowCast数组的每个元素都表示一个节点到起点的最短路径长度。而 path数组记录了每个节点的前一个节点,所以path数组的每个元素都表示一个节点的前一个节点。

在上述例子中,lowCast数组中的元素分别为0、8、9、7、5,分别表示节点0到节点A、B、C、D、E的最短路径长度。

path数组中的元素分别为-1、4、1、4、0,分别表示节点A、B、C、D、E的前一个节点。其中,-1表示没有前一个节点,即节点0的前一个节点是-1。节点 1 的前一个及诶单是 4 等等

通过这两个数组,我们可以找到从节点0到节点4的最短路径,即:从节点0到节点4的最短路径长度为8。节点 0 到节点 4 的路径是 0->4

先在 path 数组中找到第 4 个元素,他的值表示前驱节点,path[4] 刚好是 0, 所以节点 0 到节点 4 的路径是 0->4

下面再试试找节点 0 到节点 3 的路径 。先在先在 path 数组中找到第 3 个元素,path[3] 的值是 4,表示 3 的前驱节点是 4 。然后找节点 4 的前驱,一直往前找,直到找到节点 0 。我们已经知道节点 4 的前驱是 0,就直接得到节点 0 到节点 3 的路径是 0->3->4.

这两个数组就是这样用的。但有些麻烦,可以用代码打印出最短的路径

打印最短路径

javascript 复制代码
const findPath = (node) => {
   // 定义一个变量castValue,表示从节点node到起点的最短路径长度
   const castValue = lowCast[node];
   // 定义一个空数组castPath,用于存储从节点node到起点的最短路径
   const castPath = [];
   // 使用while循环,当node不等于-1时,执行以下操作:
   while (node !== -1) {
       // 将node添加到castPath数组的开头
       castPath.unshift(node);
       // 将node更新为path数组中node所指向的节点
       node = path[node];
   }
   // 输出从节点node到起点的最短路径长度castValue
   console.log("the path cast is: ", castValue);
   // 输出从节点node到起点的最短路径castPath
   console.log("the path is : ", castPath);
};

代码很简单,就不解释了

执行代码

javascript 复制代码
findPath(2);
// the path cast is:  9
// the path is :  [ 0, 4, 1, 2 ]

findPath(3);
// the path cast is:  7
// the path is :  [ 0, 4, 3 ]

findPath(4);
// the path cast is:  5
// the path is :  [ 0, 4 ]

有了 findPath 函数,是不是方便多啦😄

可以对照着下面这张图片来看输出:

总结

这篇文章分享了 JS 代码实现 dijkstra 算法。代码清晰,注释详细,是一篇不可多的的好文章啊

之前在大学学的时候,不明觉厉,现在用代码实现一遍,发现还是很简单的,而且代码实现能够使理解更深刻。

纸上得来终觉浅,觉知此事要 coding。下篇文章分享floyd算法,喜欢可以关注一下呀

相关推荐
余额不足1213819 分钟前
C语言基础十六:枚举、c语言中文件的读写操作
linux·c语言·算法
yuanManGan2 小时前
数据结构漫游记:静态链表的实现(CPP)
数据结构·链表
火星机器人life3 小时前
基于ceres优化的3d激光雷达开源算法
算法·3d
虽千万人 吾往矣3 小时前
golang LeetCode 热题 100(动态规划)-更新中
算法·leetcode·动态规划
噢,我明白了3 小时前
同源策略:为什么XMLHttpRequest不能跨域请求资源?
javascript·跨域
sanguine__3 小时前
APIs-day2
javascript·css·css3
arnold663 小时前
华为OD E卷(100分)34-转盘寿司
算法·华为od
关你西红柿子4 小时前
小程序app封装公用顶部筛选区uv-drop-down
前端·javascript·vue.js·小程序·uv
ZZTC4 小时前
Floyd算法及其扩展应用
算法
济南小草根4 小时前
把一个Vue项目的页面打包后再另一个项目中使用
前端·javascript·vue.js