Bellman-Ford算法是一种解决最短路径问题的动态规划算法,该问题是求解从源节点到其他节点的最短路径。与Dijkstra算法不同的是,Bellman-Ford算法可以处理带有负权边的图。该算法的时间复杂度为O(V*E),其中V是节点的数量,E是边的数量。
Bellman-Ford算法的原理如下:
1. 初始化所有节点的距离为无穷大,源节点的距离为0。
2. 进行V-1次循环,每次循环遍历所有的边,对于每个边(u,v),如果通过u节点可以获得更短的距离到达v节点,则更新v节点的距离为新的更短距离。
3. 检测是否存在负权回路。再进行一次循环遍历所有的边,如果在这次循环中仍然存在节点的距离被更新,则说明存在负权回路。
使用Bellman-Ford算法可以解决以下问题:
1. 单源最短路径:给定一个源节点,求解从源节点到其他节点的最短路径和距离。
2. 检测负权回路:判断图中是否存在负权回路。
由于Bellman-Ford算法可以处理负权边,但是不能处理负权回路。如果存在负权回路,则算法会进入无限循环。因此,在实际应用中,需要注意检测负权回路的存在,可以通过增加一个计数器来限制循环次数,避免死循环的发生。
优点:
1. 适用范围广:Bellman-Ford算法适用于有向图和带负权边的情况,这使得它在实际应用中非常有用。
2. 可以处理负权边:相比于Dijkstra算法,Bellman-Ford算法可以处理负权边。这使得它在某些场景下更为适用。
3. 可以检测负权环:Bellman-Ford算法可以检测是否存在负权环。如果存在负权环,则说明图中不存在最短路径。
缺点:
1. 时间复杂度高:Bellman-Ford算法的时间复杂度为O(VE),其中V是顶点数,E是边数。这使得它在有大量顶点和边的图中效率较低。
Bellman-Ford算法的代码实现
#include <bits/stdc++.h>
#define ll long long
#define endl "\n"
#define KUI ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
using namespace std;
struct jgt
{
int go;
int d;
};
const int con = 1e5 + 4;
const int inf = 2139062143;
int n, m, k;
bool vis[con];
int dis[con];
vector<struct jgt> v[con];
bool BellmanFord()
{
dis[1] = 0; // 更新根节点距离;
bool bj = 1; // 标记过程中是否发生更新节点的行为;
for (int i = 1; i <= n; i++)
{
bj = 1; // 初始化标记;
for (int j = 1; j <= n; j++) // n次遍历整个图;
{
if (dis[j] == inf) // 如果该节点距离仍为最大值,说明没有走到该节点,跳过该节点;
{
continue;
}
for (auto &x : v[j])
{
if (dis[x.go] > dis[j] + x.d)
{
dis[x.go] = dis[j] + x.d;
bj = 0; // 如果发生了节点距离变化的过程,更新标记;
}
}
}
if (bj == 1) // 标记没有被更新,说明没有节点的距离发生变化,图的最短距离已经计算完成;
{
return bj;
}
}
return bj;
// 若图无负环,n次遍历必定可以计算完成最短距离,否则即为图中存在负环;
}
void take()
{
cin >> n >> m;
memset(dis, 127, sizeof dis); // 将dis赋值为较大数;
int a1;
struct jgt ans;
for (int i = 1; i <= m; i++)
{
cin >> a1 >> ans.go >> ans.d;
v[a1].push_back(ans); // 存图路径;
}
bool pd = BellmanFord();
if (pd == 1) // 如果返回值为1,则代表最终BellmanFord函数没有再更新节点,说明图没有包含负环;
{
cout << "无环" << endl;
}
else
{
cout << "有环" << endl;
}
for (int i = 1; i <= n; i++)
{
cout << dis[i] << endl;
}
}
int main()
{
KUI;
int t1 = 1;
// cin >> t1;
while (t1--)
{
take();
}
return 0;
}
利用队列优化Bellman-Ford算法:
因为只有改变过距离的节点,之后的节点的距离才会有变化,
所以,我们只需要队列保存距离改变过的点,从而更新距离发生改变的以后的点;
#include <bits/stdc++.h>
#define ll long long
#define endl "\n"
#define KUI ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
using namespace std;
struct jgt
{
int go;
int d;
};
const int con = 1e5 + 4;
const int inf = 2139062143;
int n, m, k;
bool vis[con];
int dis[con], cont[con]; // cont[i]记录到达i节点经历的边;
vector<struct jgt> v[con];
queue<int> q;
bool BellmanFord()
{
dis[1] = 0; // 更新根节点距离;
vis[1] = 1;
q.push(1); // 入队根节点,标记根节点;
while (q.size() > 0)
{
int ans = q.front();
vis[ans] = 0;
q.pop(); // 节点出队,取消标记节点;
for (auto &x : v[ans])
{
if (dis[x.go] > dis[ans] + x.d)
{
dis[x.go] = dis[ans] + x.d;
if (vis[x.go] == 0) // 节点不在队中则入队,如果节点在队中则不需要入队;
{ // 如果节点已经在队中,那么距离总会更新;
q.push(x.go);
vis[x.go] = 1;
}
cont[x.go] = cont[ans] + 1;
if (cont[x.go] >= n) // 如果到达该节点的边数大于等于节点数,说明有节点重复走过了,存在负环;
{
return 0;
}
}
}
}
return 1;
// 若图无负环,n次遍历必定可以计算完成最短距离,否则即为图中存在负环;
}
void take()
{
cin >> n >> m;
memset(dis, 127, sizeof dis); // 将dis赋值为较大数;
int a1;
struct jgt ans;
for (int i = 1; i <= m; i++)
{
cin >> a1 >> ans.go >> ans.d;
v[a1].push_back(ans); // 存图路径;
}
bool pd = BellmanFord();
if (pd == 1) // 如果返回值为1,则代表最终BellmanFord函数没有再更新节点,说明图没有包含负环;
{
cout << "无环" << endl;
}
else
{
cout << "有环" << endl;
}
for (int i = 1; i <= n; i++)
{
cout << dis[i] << endl;
}
}
int main()
{
KUI;
int t1 = 1;
// cin >> t1;
while (t1--)
{
take();
}
return 0;
}