Bellman-Ford解决单源最短路径(负权边)
1.案例描述
求下图1号节点到所有节点之间的最短距离。
给出的边的信息如下(节点u—>节点v,边的权重是w):
u v w
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3
2.数据结构
将所有的边的信息存储于数组中,其中数组u[i]表示开始节点,数组v[i]表示结束节点,w[i]表示从节点u[i]到节点v[i]的权重。数组dis存储1号顶点(源点)到所有顶点的距离。四个数组的初始化状态均如下:
因为初始化数组dis的时候默认为顶点1还没有边相连,所以设置顶点1到其余各个顶点的距离均为∞。
3.算法思想与具体步骤
算法的基本思想为:对所有的边进行n-1次“松弛”操作。所谓“松弛”操作就是每次加入新的节点i,将节点i作为中间节点,判断源点—>节点i—>各个节点的最短距离 较之于 上一次的最短距离是否会有更新。
在案例中,具体过程可以理解为如下几个步骤:
-
数据结构的初始化(见第2章),如下图:
可以认为当前的图像只有一个节点,如下所示:
-
第①轮对所有边进行松弛。
边信息 | 松弛过程 | 是否更新 |
---|---|---|
节点2—>节点3,w= 2 | dis[3] = ∞;(dis[2] = ∞) +( w = 2),不能使dis[3]变小(初始化1->3和路径1->2->3比较) | 否 |
节点1—>节点2,w= -3 | dis[2] = ∞;(dis[1] = 0) +( w = -3) = -3,能使dis[2]变小(初始化1->2和路径1->2比较) | 更新dis[2] = -3 |
节点1—>节点5,w= 5 | dis[5] = ∞ ; (dis[1] = 0) +( w = 5) = 5,能使dis[5]变小(初始化1->5和路径1->5比较) | 更新dis[5] = 5 |
节点4—>节点5,w= 2 | dis[5] = ∞;(dis[4] = ∞ )+ (w = 2),不能使dis[5]变小(初始化1->5和路径1->4->3比较) | 否 |
节点3—>节点4,w= 3 | dis[4] = ∞;(dis[3] = ∞ )+ (w = 3),不能使dis[4]变小(初始化1->5和路径1->3->4比较) | 否 |
按照如上所述,数据结构更新为:
3. 第②轮对所有边进行松弛。
边信息 | 松弛过程 | 是否更新 |
---|---|---|
节点2—>节点3,w= 2 | dis[3] = ∞;(dis[2] = -3) +( w = 2) = -1,可以使dis[3]变小(初始化1->3和路径1->2->3比较) | 更新dis[3] = -1 |
节点1—>节点2,w= -3 | dis[2] = -3;(dis[1] = 0) +( w = -3) = -3,不能使dis[2]变小(同一条路径原地比较) | 否 |
节点1—>节点5,w= 5 | dis[5] = 5 ; (dis[1] = 0) +( w = 5) = 5,不能使dis[5]变小(同一条路径原地比较) | 否 |
节点4—>节点5,w= 2 | dis[5] = 5;(dis[4] = ∞ )+ (w = 2),不能使dis[5]变小(1->5和路径1->4->5比较) | 否 |
节点3—>节点4,w= 3 | dis[4] = ∞;(dis[3] = -1 )+ (w = 3) = 2,能使dis[4]变小(初始化1->4和路径1->3->4比较),dis[3] = -1 )为这一步更新 | 更新dis[4] = 2 |
按照如上所述,数据结构更新为:
4. 第③轮对所有边进行松弛。
边信息 | 松弛过程 | 是否更新 |
---|---|---|
节点2—>节点3,w= 2 | dis[3] = -1;(dis[2] = -3) +( w = 2) = -1,不能使dis[3]变小(同一条路径原地比较) | 否 |
节点1—>节点2,w= -3 | dis[2] = -3;(dis[1] = 0) +( w = -3) = -3,不能使dis[2]变小(同一条路径原地比较) | 否 |
节点1—>节点5,w= 5 | dis[5] = 5 ; (dis[1] = 0) +( w = 5) = 5,不能使dis[5]变小(同一条路径原地比较) | 否 |
节点4—>节点5,w= 2 | dis[5] = 5;(dis[4] = 2 )+ (w = 2) = 4,能使dis[5]变小(1->5和路径路径1->2->3->4->5比较) | 更新 dis[5] = 4 |
节点3—>节点4,w= 3 | dis[4] = 2;(dis[3] = -1 )+ (w = 3) = 2,不能使dis[4]变小(同一条路径原地比较) | 否 |
按照如上所述,数据结构更新为:
5. 第④轮对所有边进行松弛。
边信息 | 松弛过程 | 是否更新 |
---|---|---|
节点2—>节点3,w= 2 | dis[3] = -1;(dis[2] = -3) +( w = 2) = -1,不能使dis[3]变小(同一条路径原地比较) | 否 |
节点1—>节点2,w= -3 | dis[2] = -3;(dis[1] = 0) +( w = -3) = -3,不能使dis[2]变小(同一条路径原地比较) | 否 |
节点1—>节点5,w= 5 | dis[5] = 5 ; (dis[1] = 0) +( w = 5) = 5,不能使dis[5]变小(同一条路径原地比较) | 否 |
节点4—>节点5,w= 2 | dis[5] = 5;(dis[4] = 2 )+ (w = 2) = 4,不能使dis[5]变小(同一条路径原地比较) | 否 |
节点3—>节点4,w= 3 | dis[4] = 2;(dis[3] = -1 )+ (w = 3) = 2,不能使dis[4]变小(同一条路径原地比较) | 否 |
至此,所有更新均已完成。
总结
第①轮在对所有的边进行松弛之后,得到的是从1号节点“只能经过一条边”到达其余各顶点的最短路径长度。
第②轮在对所有的边进行松弛之后,得到的是从1号节点“最多经过两条边”到达其余各顶点的最短路径长度。
………………
第k轮在对所有的边进行松弛之后,得到的是从1号节点“最多经过k条边”到达其余各顶点的最短路径长度。
总共需要经过vertice - 1轮就可以得到源点到其他所有节点之间的最短距离,因为任意两点之间的最短路径最多包含vertice - 1个边。
4.回路的判断,最短路径与回路的判断
该算法也可用于判断图中是否有回路。
回路分为正权回路(回路权值之和为正)和负权回路(回路权值之和为负)。
case1:如果最短路径中包含正权回路,那么去掉这个这个回路,一定可以得到更短的路径。
case2:如果最短路径中包含负权回路,那么肯定没有最短路径,因为每多走一次负权回路就可以得到更短的路径。
因此,最短路径肯定不包含回路。
那么如何判断是否有负权回路,见第5部分的代码。
5.具体实现
#include <vector>
#include <iostream>
using namespace std;
class Bellman_Ford
{
private:
int vertice = 0;//顶点数
int edge = 0;//边数
vector<int> u;
vector<int> v;
vector<int> w;
vector<int> dis;//源点到各个顶点之间的最短距离
public:
//根据节点值和边值初始化:边的起始节点数组u,边的终止节点数组v,边u[i]->v[i]的权重w
Bellman_Ford(int x, int y) :vertice(x), edge(y)
{
//图的初始化从下标1开始
dis.resize(vertice + 1);
u.resize(edge + 1);
v.resize(edge + 1);
w.resize(edge + 1);
}
//检测负权回路
bool Detect_negative_weight_circuit()
{
bool flag = false;
for (int i = 1; i <= edge; i++)
{
if (dis[v[i]] > dis[u[i]] + w[i])
{
flag = 1;
}
}
return flag;
}
//读入图的边,并且根据边的信息初始化数组dis,数组book
void GetEdgeInfo()
{
cout << "输入边的信息(节点1,节点2,权重):" << endl;
int e1 = 0, e2 = 0, weigth = 0;
for (int i = 1; i <= edge; i++)
{
cin >> e1 >> e2 >> weigth;
u[i] = e1;
v[i] = e2;
w[i] = weigth;
}
for (int i = 2; i <= vertice; i++)
{
//dis[1]在构造函数里面已经初始化为0
dis[i] = INT_MAX;
}
}
//打印
void Print()
{
for (int i = 1; i <= vertice; i++)
{
cout << dis[i] << " ";
}
cout << endl;
}
//Bellman_Ford核心思想
void Bellman_Ford_Alg()
{
for (int k = 1; k < vertice; k++)//控制松弛的轮数
{
bool check = false;//标记在本轮松弛中数组dis是否会发送更新
//找离1号节点最近的节点(找数组dis中的最小值)
for (int i = 1; i <= edge; i++)
{
if (dis[u[i]] < INT_MAX && dis[v[i]] > (dis[u[i]] + w[i]))
{
dis[v[i]] = (dis[u[i]] + w[i]);
check = true;//如果数组dis发生变化,check的值就改变
}
}
//松弛结束后判断dis数组是否发生变化
if (check == false)
{
break;
}
}
}
};
int main()
{
Bellman_Ford Bellman(5, 5);
Bellman.GetEdgeInfo();
cout << "初始信息:" << endl;
Bellman.Print();
Bellman.Bellman_Ford_Alg();
cout << "单源最短路径(顶点1到其余各顶点):" << endl;
Bellman.Print();
bool tag = Bellman.Detect_negative_weight_circuit();
if (tag)
{
cout << "存在负权回路" << endl;
}
else
{
cout << "不存在负权回路" << endl;
}
return 0;
}
6.总结
时间复杂度:O(vertice * edge)