Bellman-Ford && SPFA
作为最短路的其他几种解法,是很有必要掌握的,已知的Dijkstra只能够解决不存在负权边的图形(其灵魂是在确定点的时候同时预言了其后面一定是大于零的边),而 Floyed 算法的时间复杂度决定了其使用的范围。所以在遇到负权边的时候就要用到Bellman-Ford或者是SPFA了,可以说其是对于 Dijkstra 算法的一个简化,没有了寻找最小路径长度点然后加入集合的过程。Bellman每次都暴力搜索所有顶点,直到没有点再被更新为止,SPFA遇到更新的点就加入队列,直到队列为空为止,当然也可以使用栈。
这是写的两个简单的程序:
// Bellman-Ford #include <cstdio> #include <iostream> #include <vector> #include <cstring> #define INF 0x7f7f7f7f using namespace std; struct Node { int pos; int dis; }info; int dis[1001], path[1001]; bool Bellman_Ford( int N, int M, vector< Node >vec[] ) { vector< Node >::iterator it; for( int i= 1; i<= N; ++i ) { dis[i]= INF; path[i]= -1; } dis[0]= 0; for( int i= 1; i<= M; ++i ) { bool finish= true; for( int j= 0; j< N; ++j ) { for( it= vec[j].begin(); it!= vec[j].end(); ++it ) { if( dis[j]> dis[ it->pos ]+ it->dis ) { finish= false; // 这时由于每次如果有边进行更新的话,那么其可能为其他边提供更好的路径 dis[j]= dis[ it->pos ]+ it->dis; path[j]= it->pos; } } } // 接下来是判定是否有负环的存在,如果存在负环,那么将提供一条路径使得接触到该环的所有点的距离变成无群小,这显然不和题义 // 证明其并不是在真正意义上按照边数递增的原则来更新边 if( finish ) { return true; } } for( int j= 1; j<= N; ++j ) { for( it= vec[j].begin(); it!= vec[j].end(); ++it ) { if( dis[j]> dis[ it->pos ]+ it->dis ) { return false; } } } } int main( ) { int N, M; while( scanf( "%d %d", &N, &M ), N| M ) { vector< Node >vec[1001]; for( int i= 2; i<= N; ++i ) { int x, y, z; scanf( "%d %d %d", &x, &y, &z ); info.pos= x- 1, info.dis= z; vec[y- 1].push_back( info ); // 这里需要建立一个逆邻接表 info.pos= y- 1; vec[x- 1].push_back( info ); } Bellman_Ford( N, M, vec ); /* if( Bellman_Ford( N, M, vec ) ) { for( int i= 0; i< N; ++i ) { printf( "The shortest path to vertex 1 for vertex %d is %d\n", i, dis[i] ); } for( int i= 0; i< N; ++i ) { printf( "path[%d]= %d\n", i, path[i] ); } } else { puts( "No shortest path exsit!" ); } */ printf( "%d\n", dis[N- 1] ); } }
// SPFA #include <cstdio> #include <cstring> #include <iostream> #include <vector> #include <queue> #define INF 0x7f7f7f7f using namespace std; struct Node { int pos, dis; }info; int dis[1001], path[1001], ti[1005], inqu[1005]; bool SPFA( int N, int M, vector< Node >vec[] ) { vector< Node >::iterator it; for( int i= 0; i< N; ++i ) { dis[i]= INF; inqu[i]= 0; ti[i]= 0; } dis[0]= 0; queue< int >q; q.push( 0 ); inqu[0]= 1; while( !q.empty() ) { int pos= q.front(); q.pop(); inqu[pos]= 0; for( it= vec[pos].begin(); it!= vec[pos].end(); ++it ) { if( dis[ it->pos ]> dis[ pos ]+ it->dis ) { dis[ it->pos ]= dis[ pos ]+ it->dis; // 在其基础上还需加上一个是否已在队列中的一个判断以及判定是否入队次数已经超过N次(是否产生负向环) // 如果已经在队列中则没有必要再将其入队,如果继续入队的话,就算是重复更新了 if( !inqu[ it->pos ] ) { q.push( it->pos ); ti[ it->pos ]++; if( ti[ it->pos ]> N ) { return false; } } } } } return true; } int main() { int N, M; while( scanf( "%d %d", &N, &M ), N| M ) { vector< Node >vec[1001]; for( int i= 1; i<= M; ++i ) { int x, y, z; scanf( "%d %d %d", &x, &y, &z ); info.pos= y- 1, info.dis= z; vec[x- 1].push_back( info ); // 这里采用的是一般的邻接表,而非bellman_ford中的逆邻接表,原因是该算法是主动性更新的 info.pos= x- 1; vec[y- 1].push_back( info ); } SPFA( N, M, vec ); /* for( int i= 0; i< N; ++i ) { printf( "dis[%d]= %d\n", i, dis[i] ); } */ printf( "%d\n", dis[N- 1] ); } }