非常感谢你的问题。因为你的问题我又重新review了一遍我的bellman-ford代码,确实有一些bug。我已经修复了。在这里尝试重新讲解一遍代码。
一般简单的实现Bellman-Ford算法(包括之前的Dijkstra算法),都是用MAX_INT的值来表示无穷。这样如果distTo[x] = MAX_INT,就表示从起始点s到x这个点还不可达。基于这样的想法Bellman-Ford算法可以这样来写:
// 以下内容为伪码理解意思即可...
// 初始化起始点s到其余所有点i均不可达, 其距离为MAX_INT
for( int i = 0 ; i < V ; i ++ )
distTo[i] = MAX_INT;
distTo[s] = 0; // 起始点s到自己的距离为0
// Bellman-Ford的过程
// 进行V-1次循环, 每一次循环求出从起点到其余所有点, 最多使用pass步可到达的最短距离
for( int pass = 1 ; pass < G.V() ; pass ++ ){
// 每次循环中对所有的边进行一遍松弛操作
for( e in E )
if( distTo[e->v()] + e->wt() < distTo[e->w()]) )
distTo[e->w()] = distTo[e->v()] + e->wt();
}
上面的代码由于使用伪码,所以代码行数比较少,思路看起来应该更清晰。这里注意:在Bellman-Ford的pass那重循环里,由于distTo初始使用了MAX_INT,所以我们可以很方便的直接判断 if( distTo[e->v()] + e->wt() < distTo[e->w()]) ) 。但是在实际情况下,使用MAX_INT有很多不方便的地方(比如容易整型溢出)。因为在计算机里,这个数字毕竟不是真正的“无穷”。所以在我们课程的代码里,可以使用from这个数组来看:对于每一个e,e->v()这个节点是否在之前的遍历中已经可达了。如果是已经可达的节点,才可以进一步判断当前是否可以进行这个松弛操作。在我课程讲解的代码中缺少了对e->v()这个节点是否可达的判断。
另外,你观察到在我们的课程代码中,第二重循环又对所有的顶点进行了一次遍历。这其实是基于我们自己实现的这个Graph类,尝试对图中的所有边进行遍历的方式:先遍历所有顶点,再遍历所有顶点对应的邻边。使用这样的方式,对于无向图来说,其实每个边都被遍历了两次,不过这个小问题并不影响整个算法的正确性。如果有兴趣,也可以思考一下为Graph类添加一个针对图中所有的边进行遍历的迭代器:)是一个不错的练习,也能加深对迭代器的认识:)
我修改过的Bellman-Ford代码如下,加入了更多注释。希望你可以理解,有任何问题随时交流:)
BellmanFord(Graph &graph, int s):G(graph){
this->s = s;
distTo = new Weight[G.V()];
// 初始化所有的节点s都不可达, 由from数组来表示
for( int i = 0 ; i < G.V() ; i ++ )
from.push_back(NULL);
// 设置distTo[s] = 0, 并且让from[s]不为NULL, 表示初始s节点可达且距离为0
distTo[s] = Weight();
from[s] = new Edge<Weight>(); // 这里我们from[s]的内容是new出来的, 注意要在析构函数里delete掉
// Bellman-Ford的过程
// 进行V-1次循环, 每一次循环求出从起点到其余所有点, 最多使用pass步可到达的最短距离
for( int pass = 1 ; pass < G.V() ; pass ++ ){
// 每次循环中对所有的边进行一遍松弛操作
// 遍历所有边的方式是: 先遍历所有的顶点, 然后遍历和所有顶点相邻的所有边
for( int i = 0 ; i < G.V() ; i ++ ){
// 使用我们实现的邻边迭代器, 遍历和所有顶点相邻的所有边
typename Graph::adjIterator adj(G,i);
for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() )
// 对于每一个边首先判断e->v()可达
// 之后看: 如果e->w()以前没有到达过, 显然我们可以更新distTo[e->w()]
// 或者e->w()以前虽然到达过, 但是通过这个e, 我们可以获得一个更短的距离,
// 即可以进行一次松弛操作, 我们也可以更新distTo[e->w()]
if( from[e->v()] &&
(!from[e->w()] || distTo[e->v()] + e->wt() < distTo[e->w()]) ){
distTo[e->w()] = distTo[e->v()] + e->wt();
from[e->w()] = e;
}
}
}
hasNegativeCycle = detectNegativeCycle();
}
同时,课程的官方github代码进行了更新。有兴趣可以查阅:
https://github.com/liuyubobobo/Play-with-Algorithms/blob/master/09-Shortest-Path/Course%20Code%20(C%2B%2B)/05-Implementation-of-Bellman-Ford/BellmanFord.h
再次感谢你的问题。非常抱歉我的代码中的bug让你困惑了。如果愿意,可以加我的微信:liuyubobobo。请注明慕课网算法课程,我会发给你一个小红包:)