图由节点和边组成,边有方向的图称为有向图,边没有方向的图称为无向图,最短路径算法里可以把无向图视为双向连接的有向图。
边有权重的图称为有权图,边没有权重的图称为无权图,无权图可以视为边的权重均为1的图。
给定图中的一个节点,求该节点到其他所有节点的最短路径。
Bellman-Ford属于DP算法,适用于含负权边的图,并且可以检测图中是否含有负权环(意味着没有最短路径),时间复杂度为O(VE)
对所有边进行V-1次遍历,第一次遍历获得边数为1的最短路径,第二次遍历获得边数为2的最短路径,以此类推,第V-1次遍历获得边数为V-1的最短路径。V-1次遍历后,进行第V次遍历,如果能够获得更小的路径长度,说明存在负环。
dis[]用于存储每个节点到源点src的距离,preNode[]存储每个节点的前驱节点
Step1:初始化dis[]为INT_MAX,dis[src]初始化为0. preNode[src]初始化为-1.
Step2:遍历图中的每条边edge(v,u),若dis[v]不为INT_MAX,且dis[v] + weight(v,u) < dis[u],执行dis[u] = dis[v] + weight(v,u)。
Step3:重复Step2 V次,若某次遍历没有执行dis[u] = dis[v] + weight(v,u),即发生了松弛,结束Step3
Step4:判断Step3最后一次执行是否发生松弛,若发生,说明图中含有负权环,若没有发生,说明图中没有负权环。
可以从直观上理解,第一次遍历获得了(距离源点)边数量为1的最短路径,第二次在第一次的基础上获得了边数量为2的最短路径,第三次在第二次的基础上获得了边数量为3的最短路径,依次类推,有些节点到源点有很多条路径,这些路径的边数可能不一样也可能一样,算法会在V-1循环中比较这些路径的长度,取最小值作为最短路径(遍历中边数多的可能距离可能反而小于边数少的,这个时候就会发生松弛了)。
有两种情况会导致Step3结束,一是当次遍历没有发生松弛,二是已经循环了V次,情况一显然不存在负环,不然肯定可以继续松弛。情况二下,如果最后一次遍历发生了松弛,说明第V次松弛了,意味着找到的边长数为V的最短路径,而最长的最短路劲应该是V-1条边, 所以肯定至少一条边重复了,一定是出现环了,环路径比非环更短,说明是负环。
举个最简单的例子:
第一次遍历 dis[1] = 2, dis[2] =INT_MAX
第二次遍历 dis[1] = 2, dis[2] = 3;
已经遍历了V-1即两次了,第3次遍历,dis[1] = dis[2] + wegiht(2,1) = 1,小于dis[1],所以会继续发生松弛。
如果weight(1,2) = 3,环长度为3-2=1,正环,这个时候第V次遍历就不会发生松弛了。
#include
#include
using namespace std;
class Solution
{
public:
vector<int> bellmanFord(int src, vector<vector<int>> &graph, vector<int> &preNode)
{
preNode = vector<int>(graph.size(), -1);
///transform to vector>
vectorint , int>> arcs;
for(int i = 0; i < graph.size(); i++)
{
for(int j = 0; j < graph.size(); j++)
{
if(graph[i][j] != 0)
{
arcs.push_back(pair<int, int>(i, j));
}
}
}
///intialize distance array
vector<int> distToSrc(graph.size(), INT_MAX);
distToSrc[src] = 0;
bool isSlacked = false;
///traverse and slack
for(int i = 0; i < graph.size(); i++)
{
isSlacked = false;
for(auto arc: arcs)
{
if(distToSrc[arc.first] != INT_MAX &&
distToSrc[arc.first] + graph[arc.first][arc.second] < distToSrc[arc.second])
{
distToSrc[arc.second] = distToSrc[arc.first] + graph[arc.first][arc.second];
preNode[arc.second] = arc.first;
isSlacked = true;
}
}
///no slack in on traverse, break;
if(!isSlacked)
{
break;
}
}
/*
*ifSlacked == true, means V's traverse is slacked which means
*have negative circle. otherwise isSlacked should be false
*/
if(isSlacked)
{
return vector<int>();
}
return distToSrc;
}
int printPath()
{
vector<vector<int>> graph{{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}};
vector<int> preNode;
auto dists = bellmanFord(0, graph, preNode);
///print
for(int i = 0; i < dists.size(); i++)
{
cout << i << " " << dists[i] << ": ";
int node = i;
stack<int> s;
while(node != -1)
{
s.push(node);
node = preNode[node];
}
while(!s.empty())
{
cout << s.top() << " ";
s.pop();
}
cout << endl;
}
}
int main()
{
Solution().printPath();
return 0;
}