最短路径算法——Dijkstra算法的JS实现

一、Dijkstra算法的思路

Dijkstra算法是针对单源点求最短路径的算法。

其主要思路如下:

1. 将顶点分为两部分:已经知道当前最短路径的顶点集合Q和无法到达顶点集合R。

2. 定义一个距离数组(distance)记录源点到各顶点的距离,下标表示顶点,元素值为距离。源点(start)到自身的距离为0,源点无法到达的顶点的距离就是一个大数(比如Infinity)。

3. 以距离数组中值为非Infinity且值最小(即当前距离最短)的顶点V为中转跳点,假设V跳转至顶点W的距离加上顶点V至源点的距离还小于顶点W至源点的距离,那么就可以更新顶点W至源点的距离。即下面distance[V] + matrix[V][W] < distance[W],那么distance[W] = distance[V] + matrix[V][W]。

4. 重复上一步骤,即遍历距离数组,同时无法到达顶点集合R为空。

二、具体例子

偷个懒,直接用上一篇博客《最小生成树算法——Prim算法和Kruskal算法的JS实现》的图为例子。

它的邻接矩阵如下:

求解步骤 

第一步:假设源点为V0,那么目前最短路径的顶点集合Q中就只有{V0}和无法到达顶点集合R中有{V1, V2, V3, V4}

第二步:初始化distance数组,就是下面这样

第三步:以distance数组中值为非Infinity且值最小的顶点为中转跳点,这一步就是V0,依照如果distance[V] + matrix[V][W] < distance[W],那么distance[W] = distance[V] + matrix[V][W]的规则,distance数组就会变成下面这样,同时集合Q变成了{V0, V1, V2, V4},集合R变成了{V3},即已访问数组为[true, true, true, false, true]。同时计算出当前距离源点最短的节点,即V2。

第四步:重复第三步的方法,然后变成以V2为中转跳点,更新距离数组,同时更新已访问数组,变成[true, true, true, true, true]。

之后同理,遍历所有节点之后,输出

三、代码实现

这个代码没有考虑权值为负数的情况,还没验证负数的情况,目前是按照权值为正数实现的,之后考虑完善。 

同时这是针对单源点求最短路径,如果求全图各顶点的最短路径,只需要遍历顶点然后使用Dijkstra算法,这样算上Dijkstra算法本身的时间复杂度,总的复杂度会是O(n^3)。

/**
 * Dijkstra算法:单源最短路径
 * 思路:
 * 1. 将顶点分为两部分:已经知道当前最短路径的顶点集合Q和无法到达顶点集合R。
 * 2. 定义一个距离数组(distance)记录源点到各顶点的距离,下标表示顶点,元素值为距离。源点(start)到自身的距离为0,源点无法到达的顶点的距离就是一个大数(比如Infinity)。
 * 3. 以距离数组中值为非Infinity的顶点V为中转跳点,假设V跳转至顶点W的距离加上顶点V至源点的距离还小于顶点W至源点的距离,那么就可以更新顶点W至源点的距离。即下面distance[V] + matrix[V][W] < distance[W],那么distance[W] = distance[V] + matrix[V][W]。
 * 4. 重复上一步骤,即遍历距离数组,同时无法到达顶点集合R为空。
 *
 * @param matrix 邻接矩阵,表示图
 * @param start 起点
 *
 *
 *
 * 如果求全图各顶点作为源点的全部最短路径,则遍历使用Dijkstra算法即可,不过时间复杂度就变成O(n^3)了
 * */
function Dijkstra(matrix, start = 0) {
    const rows = matrix.length,//rows和cols一样,其实就是顶点个数
        cols = matrix[0].length;
 
    if(rows !== cols || start >= rows) return new Error("邻接矩阵错误或者源点错误");
 
    //初始化distance
    let distance = new Array(rows).fill(Infinity);
    // 初始化访问节点
    let visited = new Array(rows).fill(false);
    distance[start] = 0;
 
    for(let i = 0; i < rows; i++) {
        // 更新节点访问
        visited[start] = true
        // 达到不了的顶点不能作为中转跳点
        if(distance[start] < Infinity) {
            for(let j = 0; j < cols; j++) {
                //通过比较distance[start] + matrix[start][j]和distance[j]的大小来决定是否更新distance[j]。
                if(matrix[start][j] + distance[start] < distance[j]) {
                    distance[j] = matrix[start][j] + distance[start];
                }
            }
        }
        
        // 找到当前最短路径顶点作为中转跳点
        let minIndex = -1;
        let min = Infinity;
        for(let k = 0; k < rows; k++) {
            if (!visited[k] && distance[k] < min) {
                min = distance[k];
                minIndex = k;
            }
        }
        start = minIndex
    }
    return distance;
}

/**
 * 邻接矩阵
 * 值为顶点与顶点之间边的权值,0表示无自环,一个大数表示无边(比如10000)
 * */
const MAX_INTEGER = Infinity;//没有边或者有向图中无法到达
const MIN_INTEGER = 0;//没有自环

const matrix= [
    [MIN_INTEGER, 9, 2, MAX_INTEGER, 6],
    [9, MIN_INTEGER, 3, MAX_INTEGER, MAX_INTEGER],
    [2, 3, MIN_INTEGER, 5, MAX_INTEGER],
    [MAX_INTEGER, MAX_INTEGER, 5, MIN_INTEGER, 1],
    [6, MAX_INTEGER, MAX_INTEGER, 1, MIN_INTEGER]
];


console.log(Dijkstra(matrix, 0));//[ 0, 5, 2, 7, 6 ]

四、优化

从上面的解析步骤中可以发现,当前如果所有节点都已经访问过,其实就已经拿到最优解,此时可以结束代码,直接输出。所以最终可以优化代码为如下:

function Dijkstra(matrix, start = 0) {
    const rows = matrix.length,//rows和cols一样,其实就是顶点个数
        cols = matrix[0].length;
 
    if(rows !== cols || start >= rows) return new Error("邻接矩阵错误或者源点错误");
 
    //初始化distance
    let distance = new Array(rows).fill(Infinity);
    // 初始化访问节点
    let visited = new Array(rows).fill(false);
    distance[start] = 0;
 
    // 存在节点未访问则循环
    while(visited.some(item => !item)) {
        // 更新节点访问
        visited[start] = true
        // 达到不了的顶点不能作为中转跳点
        if(distance[start] < Infinity) {
            for(let j = 0; j < cols; j++) {
                //通过比较distance[start] + matrix[start][j]和distance[j]的大小来决定是否更新distance[j]。
                if(matrix[start][j] + distance[start] < distance[j]) {
                    distance[j] = matrix[start][j] + distance[start];
                }
            }
        }
        
        // 找到当前最短路径顶点作为中转跳点
        let minIndex = -1;
        let min = Infinity;
        for(let k = 0; k < rows; k++) {
            if (!visited[k] && distance[k] < min) {
                min = distance[k];
                minIndex = k;
            }
        }
        start = minIndex
    }
    return distance;
}

五、带路径节点

改变distance数组的元素,不在只记录距离,同时记录“中转节点”。

function Node(val, pre) {
  this.val = val // 当前距离
  this.pre = pre || null // 中转(前置)节点
}

function Dijkstra(matrix, start = 0) {
    const rows = matrix.length,//rows和cols一样,其实就是顶点个数
        cols = matrix[0].length;
 
    if(rows !== cols || start >= rows) return new Error("邻接矩阵错误或者源点错误");
 
    //初始化distance
    let distance = new Array(rows)
    for (let i =0;i<rows;++i) {
        distance[i] = new Node(Infinity)
    }
    // 初始化访问节点
    let visited = new Array(rows).fill(false);
    distance[start] = new Node(0);
 
    // 存在节点未访问则循环
    while(visited.some(item => !item)) {
        // 更新节点访问
        visited[start] = true
        // 达到不了的顶点不能作为中转跳点
        if(distance[start].val < Infinity) {
            for(let j = 0; j < cols; j++) {
                //通过比较distance[start] + matrix[start][j]和distance[j]的大小来决定是否更新distance[j]。
                if(matrix[start][j] + distance[start].val < distance[j].val) {
                    distance[j].val = matrix[start][j] + distance[start].val;
                    distance[j].pre = start
                }
            }
        }
        
        // 找到当前最短路径顶点作为中转跳点
        let minIndex = -1;
        let min = Infinity;
        for(let k = 0; k < rows; k++) {
            if (!visited[k] && distance[k].val < min) {
                min = distance[k].val;
                minIndex = k;
            }
        }
        start = minIndex
    }
    return distance;
}

 

  • 6
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
Dijkstra算法是一种常用的最短路径算法,它可以用于计算从源节点到所有其他节点的最短路径。下面是它的Java实现: ``` import java.util.*; public class DijkstraAlgorithm { private int V; // 节点数 private int[][] graph; // 图 private boolean[] visited; // 标记节点是否访问过 private int[] dist; // 存储源节点到各个节点的最短距离 public DijkstraAlgorithm(int[][] graph) { this.V = graph.length; this.graph = graph; this.visited = new boolean[V]; this.dist = new int[V]; Arrays.fill(dist, Integer.MAX_VALUE); // 初始化源节点到各个节点的距离为无穷大 } // 找到未访问节点中距离源节点最近的节点 private int findMinDistNode() { int minDist = Integer.MAX_VALUE; int minIndex = -1; for (int i = 0; i < V; i++) { if (!visited[i] && dist[i] < minDist) { minDist = dist[i]; minIndex = i; } } return minIndex; } public void dijkstra(int src) { dist[src] = 0; for (int i = 0; i < V - 1; i++) { int u = findMinDistNode(); visited[u] = true; for (int v = 0; v < V; v++) { if (!visited[v] && graph[u][v] != 0 && dist[u] != Integer.MAX_VALUE && dist[u] + graph[u][v] < dist[v]) { dist[v] = dist[u] + graph[u][v]; } } } } public static void main(String[] args) { int[][] graph = new int[][] { { 0, 4, 0, 0, 0, 0, 0, 8, 0 }, { 4, 0, 8, 0, 0, 0, 0, 11, 0 }, { 0, 8, 0, 7, 0, 4, 0, 0, 2 }, { 0, 0, 7, 0, 9, 14, 0, 0, 0 }, { 0, 0, 0, 9, 0, 10, 0, 0, 0 }, { 0, 0, 4, 14, 10, 0, 2, 0, 0 }, { 0, 0, 0, 0, 0, 2, 0, 1, 6 }, { 8, 11, 0, 0, 0, 0, 1, 0, 7 }, { 0, 0, 2, 0, 0, 0, 6, 7, 0 } }; DijkstraAlgorithm dijkstra = new DijkstraAlgorithm(graph); dijkstra.dijkstra(0); System.out.println("从源节点0到其他节点的最短距离为:"); for (int i = 0; i < graph.length; i++) { System.out.println(i + ": " + dijkstra.dist[i]); } } } ``` 这个实现中,我们用一个二维数组来表示图,其中元素graph[i][j]表示从节点i到节点j的距离;用一个boolean类型的数组visited来标记节点是否已经被访问过;用一个一维数组dist来存储源节点到各个节点的最短距离。在实现中,我们首先将源节点到各个节点的距离初始化为无穷大,然后以源节点为起点开始遍历,每次找到未访问节点中距离源节点最近的节点,将其标记为已访问,然后更新其邻居节点的距离。重复以上步骤,直到所有节点都被访问过为止。最后,我们输出源节点到各个节点的最短距离即可。 以上就是Dijkstra算法的Java实现,希望对你有帮助!
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值