我的Dijkstra的算法实现未返回最短路径 [英] My Dijkstra's algorithm implementation does not return shortest path

查看:71
本文介绍了我的Dijkstra的算法实现未返回最短路径的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图在JavaScript中实现Dijkstra的最短路径算法,并通过多个示例对其进行了测试.

I tried to implement the Dijkstra's shortest path algorithm in JavaScript, and tested it with multiple examples.

我正在使用此图查看其行为:

I am using this graph to see how it would behave:

如果我想找到从A到I的最短路径,结果应该是[A,D,C,F,G,H,I],距离等于10.

If I want to find the shortest path from A to I, the result should be [A, D, C, F, G, H, I] with distance equal to 10.

但是我的实现将路径返回为[A,B,E,J,F,G,H,I],距离为14.

But my implementation returns the path as [A, B, E, J, F, G, H, I] with distance of 14.

这是我的JavaScript代码:

Here is my JavaScript code:

const graph = {
    A: {B: 3, C: 4, D: 2},
    B: {A: 3, D: 6, E: 1},
    C: {A: 4, D: 1, F: 3},
    D: {A: 2, B: 6, C: 1, E: 5},
    E: {B: 1, D: 5, J: 1},
    F: {C: 3, G: 2, J: 5},
    G: {F: 2, H: 1, I: 3},
    H: {G: 1, I: 1, X: 2},
    I: {G: 3, H: 1, X: 8},
    J: {E: 1, F: 5, X: 6},
    X: {H: 2, I: 8, J: 6},
};

// The class Dsp:

class Dsp {
  constructor() {
    //Previous node after update of distance
    this.prev = {};
    //Distances of each node
    this.distances = {};
    //Array of unvisited neighbors
    this.unvisitedn = [];
    //Path of visited nodes from first to final node
    this.path = [];
  }

  findsp(graph, start, end) {

    //Register graph data 
    this.registerGraphData(graph, start);

    //Set the starting node as the current node
    let cn = start;

    //While there are unvisited nodes
    while (this.unvisitedn.length > 0) {
      //Mark the currentNode as visited
      this.markAsVisited(cn);

      //Compare distance from current node to unvisited neighbors
      let nodes = this.compareNodeDistances(graph, cn);

      //Update neighbor distance
      this.updateNodeDistances(nodes, cn);

      //Compare each unvisited neighbor and choose the one with the lowest distances
      //Set the choosed node as the new current node
      cn = this.selectNextNode(graph, cn);
    }

    return this.generatePath(start, end);
  }

  registerGraphData(graph, start) {

    //Set starting weight for all nodes
    const higherWeight = 10000;

    //For each node in the graph
    for (let node in graph) {
      //If the node is the starting node 
      if (node == start)
        //Set starting weight as 0
        this.distances[node] = 0;
      //else set the higherWeight
      else
        this.distances[node] = higherWeight;

      //Add to the unvisited nodes
      this.unvisitedn.push(node);
    }

    console.log(this.distances);
    console.log(this.unvisitedn);
  }

  markAsVisited(cn) {

    console.log('Visiting', cn);

    let index = this.unvisitedn.indexOf(cn);
    this.unvisitedn.splice(index, 1);
  }

  getUnvisitedNeighbors(graph, cn) {

    //All current node neighbors
    let nbs = graph[cn];
    let unbs = [];

    for (let nb in nbs) {
      if (this.unvisitedn.includes(nb))
        unbs.push(nb);
    }

    console.log(cn, 'Unvisited neighbors:', unbs);

    return unbs;
  }

  compareNodeDistances(graph, cn) {

    let unbs = this.getUnvisitedNeighbors(graph, cn);

    //new distances
    let newDistances = {};

    //For all currentNode neighbors
    for (let nb of unbs) { //Substituted unbs

      //Neighbor Weight
      let nbw = graph[cn][nb];
      //console.log('Neighbor weight', nbw);

      //neighbor distance
      let nbd = this.distances[nb];
      //console.log('Neighbor distance', nbd);

      //current node distance
      let cnd = this.distances[cn];
      //console.log('Current node distance', cnd);

      //If the neighbor distance > current node distance + neighbor weight
      if (nbd > cnd + nbw)
        newDistances[nb] = cnd + nbw;
    }

    console.log('new distances:', newDistances);

    return newDistances;
  }

  updateNodeDistances(nodes, cn) {

    //Update distances for each neighbor that was compared
    for (let node in nodes) {
      console.log(nodes);


      this.distances[node] = nodes[node];
      this.prev[node] = cn;
    }

    console.log("Node distances after update", this.distances);
    console.log("Node previous nodes after update", this.prev);
  }

  selectNextNode(graph, cn) {
    let unbs = this.getUnvisitedNeighbors(graph, cn);
    let mind = 100000;
    let nextn = null;

    //If there are unvisited neighbors
    if (unbs.length > 0) {
      for (let nb of unbs) {
        if (this.distances[nb] < mind) {
          mind = this.distances[nb];
          nextn = nb;
        }
      }
    } else {
      nextn = this.unvisitedn[0];
    }

    return nextn;
  }

  generatePath(start, end) {

    let cn = end;
    let path = {};
    let nodes = [];

    while (cn !== start) {
      nodes.push(cn);
      cn = this.prev[cn];
    }

    nodes.push(start);
    nodes.reverse();

    path['nodes'] = nodes;
    path['distance'] = this.distances[end];

    return path;
  }
}

let shp = new Dsp();

console.log(shp.findsp(graph, 'A', 'I'));

我想了解我编写的步骤有什么问题.

I would like to understand what´s wrong with the steps I programmed.

我做错了什么?还有其他步骤或考虑吗?

What am I doing wrong? Is there some additional step, or consideration?

推荐答案

问题是您没有执行最佳优先搜索.您的代码实际上执行了深度优先搜索,您只需在其中优化从 current 节点中选择的哪个未访问的 neighbor 即可.但是,您应该从所有未访问的节点中获取距离最小的节点,而不仅要从当前节点的相邻节点中获取.

The problem is that you are not performing a best-first search. Your code really performs a depth-first search, where you just optimise which unvisited neighbor you will choose from the current node. But you should take the node with the minimum distance from among all unvisited nodes, not just among the neighbors of the current node.

另请参见 Wikipedia 上算法说明的第6步:

See also step 6 of the algorithm description on Wikipedia:

  1. 否则,选择标记有最小尝试距离的未访问节点,将其设置为新的当前节点"
  1. Otherwise, select the unvisited node that is marked with the smallest tentative distance, set it as the new "current node"

所以问题出在 selectNextNode 中.可以对此进行更正:

So the problem is in selectNextNode. It could be corrected to this:

selectNextNode(graph, cn) {
    let mindist = Infinity;
    let best;
    for (let node of this.unvisitedn) {
        if (this.distances[node] < mindist) {
            mindist = this.distances[node];
            best = node;
        }
    }
    return best;
}

但是,这是一个幼稚的实现,因为在每一轮中您都必须再次找到最小值:这使得算法不是最优的.真正的Dijkstra算法将使用优先级队列(例如堆),从而使查找效率更高.

However, this is a naive implementation, as in each round you have to find the minimum again: this makes the algorithm non-optimal. A true Dijkstra algorithm will use a priority queue, such as a heap, which makes this lookup more efficient.

不幸的是,JavaScript还没有提供本机堆实现,因此我们必须自己抛出或引用一个库.我从对在Javascript中实现Priority Queue的有效方式的答案中获取了实现.有关该实现的更多详细信息,请参见此处.

Unfortunately JavaScript does not (yet) provide a native heap implementation, so we have to throw our own or reference a library. I took the implementation from my answer to Efficient way to implement Priority Queue in Javascript?. See there for more details on that implementation.

我认为最短路径算法的实现不保证使用类.像您的 findsp 这样的功能就足够了.

I think the implementation of the shortest path algorithm does not warrant the use of a class. A function like your findsp should be enough.

所以这里是:

/* MinHeap minimised - taken from https://stackoverflow.com/a/66511107/5459839 */
const MinHeap={siftDown(h,i=0,v=h[i]){if(i<h.length){let k=v[0];while(1){let j=i*2+1;if(j+1<h.length&&h[j][0]>h[j+1][0])j++;if(j>=h.length||k<=h[j][0])break;h[i]=h[j];i=j;}h[i]=v}},heapify(h){for(let i=h.length>>1;i--;)this.siftDown(h,i);return h},pop(h){return this.exchange(h,h.pop())},exchange(h,v){if(!h.length)return v;let w=h[0];this.siftDown(h,0,v);return w},push(h,v){let k=v[0],i=h.length,j;while((j=(i-1)>>1)>=0&&k<h[j][0]){h[i]=h[j];i=j}h[i]=v;return h}};

function DijkstraShortestPath(graph, start, end) {
    // Heap with one entry: distance is 0 at start, and there is no previous.
    let heap = [[0, start, null]]; 
    let prev = {};
    
    while (heap.length) {
        let [distance, current, cameFrom] = MinHeap.pop(heap);
        if (current in prev) continue; // Already visited
        prev[current] = cameFrom; // Mark as visited
        if (current == end) { // Found!
            // Reconstruct path
            let path = [];
            while (current) {
                path.push(current);
                current = prev[current];
            }
            path.reverse();
            return { path, distance };
        }
        // Push unvisited neighbors on the heap
        for (let [neighbor, edge] of Object.entries(graph[current])) {
            if (!(neighbor in prev)) MinHeap.push(heap, [distance + edge, neighbor, current]);
        }
    }
}

// Demo:
const graph = {
    A: {B: 3, C: 4, D: 2},
    B: {A: 3, D: 6, E: 1},
    C: {A: 4, D: 1, F: 3},
    D: {A: 2, B: 6, C: 1, E: 5},
    E: {B: 1, D: 5, J: 1},
    F: {C: 3, G: 2, J: 5},
    G: {F: 2, H: 1, I: 3},
    H: {G: 1, I: 1, X: 2},
    I: {G: 3, H: 1, X: 8},
    J: {E: 1, F: 5, X: 6},
    X: {H: 2, I: 8, J: 6},
}

console.log(DijkstraShortestPath(graph, 'A', 'I'));

这篇关于我的Dijkstra的算法实现未返回最短路径的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆