对图的几种初级算法做个总结,温故知新。
最短路问题
Bellman-Ford和Dijkstra的递推公式都是d[i] = min{d[j] + cost[i to j]}。

单源最短路之Bellman-Ford算法
适用于无原点s可达负圈的任意图,复杂度O(|V|*|E|)。
如果负圈存在的话,会陷入死循环。但是可以利用这一性质来判断是否有负圈:
#include <iostream>
using namespace std;
#define MAX_E 1024
// 从顶点from指向顶点to的权值为cost的边
struct edge
{
int from, to, cost;
edge(){}
edge(int from, int to, int cost)
{
this->from = from;
this->to = to;
this->cost = cost;
}
};
// 边
edge es[MAX_E];
// 最短距离
int d[MAX_E];
// V是顶点数,E是边数
int V, E;
// 求解从顶点s出发到所有点的最短距离
void shortest_path(int s)
{
memset(d, 0x3f, V * sizeof(int));
d[s] = 0;
while (true)
{
bool update = false;
for (int i = 0; i < E; ++i)
{
edge e = es[i];
if (d[e.from] != 0x3f3f3f3f && d[e.to] > d[e.from] + e.cost)
{
d[e.to] = d[e.from] + e.cost;
update = true;
}
}
if (!update)
{
break;
}
}
}
// 是否存在负圈
bool find_negative_loop()
{
memset(d, 0, sizeof(d));
for (int i = 0; i < V; ++i)
{
for (int j = 0; j < E; ++j)
{
edge e = es[j];
if (d[e.to] > d[e.from] + e.cost)
{
d[e.to] = d[e.from] + e.cost;
// 如果更新了V次,则存在负圈
if (i == V - 1)
{
return true;
}
}
}
}
return false;
}
///////////////////////////SubMain//////////////////////////////////
int main(int argc, char *argv[])
{
// 测试数据
V = 7;
E = 20;
es[0] = edge(0, 1, 2);
es[1] = edge(0, 2, 5);
es[2] = edge(1, 2, 4);
es[3] = edge(1, 3, 6);
es[4] = edge(1, 4, 10);
es[5] = edge(2, 3, 2);
es[6] = edge(3, 5, 1);
es[7] = edge(4, 5, 3);
es[8] = edge(4, 6, 5);
es[9] = edge(5, 6, 9);
// 无向图必须重复一遍
es[10] = edge(1, 0, 2);
es[11] = edge(2, 0, 5);
es[12] = edge(2, 1, 4);
es[13] = edge(3, 1, 6);
es[14] = edge(4, 1, 10);
es[15] = edge(3, 2, 2);
es[16] = edge(5, 3, 1);
es[17] = edge(5, 4, 3);
es[18] = edge(6, 4, 5);
es[19] = edge(6, 5, 9);
// 测试数据结束
cout << "negative loop ? " << boolalpha << find_negative_loop() << endl;
shortest_path(0);
cout << d[V - 1] << endl;
system("pause");
return 0;
}
///////////////////////////End Sub//////////////////////////////////
单源最短路之Dijkstra算法
Bellman-Ford机械地遍历每一条边,浪费时间。Dijkstra算法每次循环都从最短距离已经确定的顶点出发,如果用堆储存最短距离的状态的话,复杂度降低为O(|E|log|V|)。这里的“最短距离已经确定”的前提是,没有负权重使得此最短距离变小,所以Dijkstra不能用于负边。举个例子说明Dijkstra为什么不能有负边:
1->2 2
1->3 100
3->2 -99
求从1开始的单源最短路的话会得到1->2最短路是2的错误结果。这是还是需要使用Bellman-Ford算法。
一份实现:
#include <iostream>
#include <queue>
#include <functional>
using namespace std;
#define MAX_V 1024
// 从顶点from指向顶点to的权值为cost的边
struct edge
{
int to, cost;
edge(){}
edge(int to, int cost)
{
this->to = to;
this->cost = cost;
}
};
// first 最短路径,second顶点编号
typedef pair<int, int> P;
// 边
vector<edge> G[MAX_V];
// 最短距离
int d[MAX_V];
// V是顶点数,E是边数
int V, E;
// 求解从顶点s出发到所有点的最短距离
void dijkstra(int s)
{
priority_queue<P, vector<P>, greater<P> > que;
memset(d, 0x3f, V * sizeof(int));
d[s] = 0;
que.push(P(0, s));
while (!que.empty())
{
P p = que.top(); que.pop();
int v = p.second;
if (d[v] < p.first) continue;
for (int i = 0; i < G[v].size(); ++i)
{
edge e = G[v][i];
if (d[e.to] > d[v] + e.cost)
{
d[e.to] = d[v] + e.cost;
que.push(P(d[e.to], e.to));
}
}
}
}
///////////////////////////SubMain//////////////////////////////////
int main(int argc, char *argv[])
{
// 测试数据
V = 7;
E = 20;
G[0].push_back(edge(1, 2));
G[1].push_back(edge(0, 2));
G[0].push_back(edge(2, 5));
G[2].push_back(edge(0, 5));
G[1].push_back(edge(2, 4));
G[2].push_back(edge(1, 4));
G[1].push_back(edge(3, 6));
G[3].push_back(edge(1, 6));
G[1].push_back(edge(4, 10));
G[4].push_back(edge(1, 10));
G[2].push_back(edge(3, 2));
G[3].push_back(edge(2, 2));
G[3].push_back(edge(5, 1));
G[5].push_back(edge(3, 1));
G[4].push_back(edge(5, 3));
G[5].push_back(edge(4, 3));
G[4].push_back(edge(6, 5));
G[6].push_back(edge(4, 5));
G[5].push_back(edge(6, 9));
G[6].push_back(edge(5, 9));
// 测试数据结束
dijkstra(0);
cout << d[V - 1] << endl;
system("pause");
return 0;
}
///////////////////////////End Sub//////////////////////////////////
路径还原
如果要求解最短路径途经的各点,只需定义一个prev[j]来记录最短路径上顶点j的前驱即可。
#include <iostream>
#include <queue>
#include <functional>
#include <vector>
#include <iterator>
using namespace std;
#define MAX_V 1024
// 从顶点from指向顶点to的权值为cost的边
struct edge
{
int to, cost;
edge(){}
edge(int to, int cost)
{
this->to = to;
this->cost = cost;
}
};
// first 最短路径,second顶点编号
typedef pair<int, int> P;
// 边
vector<edge> G[MAX_V];
// 最短距离
int d[MAX_V];
// V是顶点数,E是边数
int V, E;
int prev_vertex[MAX_V]; // 最短路上的前驱顶点
// 求解从顶点s出发到所有点的最短距离
void dijkstra(int s)
{
priority_queue<P, vector<P>, greater<P> > que;
memset(d, 0x3f, V * sizeof(int));
d[s] = 0;
que.push(P(0, s));
while (!que.empty())
{
P p = que.top(); que.pop();
int v = p.second;
if (d[v] < p.first) continue;
for (int i = 0; i < G[v].size(); ++i)
{
edge e = G[v][i];
if (d[e.to] > d[v] + e.cost)
{
d[e.to] = d[v] + e.cost;
que.push(P(d[e.to], e.to));
prev_vertex[e.to] = v;
}
}
}
}
// 到顶点t的最短路
vector<int> get_path(int t)
{
vector<int> path;
for (; t != -1; t = prev_vertex[t])
{
path.push_back(t);
}
reverse(path.begin(), path.end());
return path;
}
///////////////////////////SubMain//////////////////////////////////
int main(int argc, char *argv[])
{
prev_vertex[0] = -1;
// 测试数据
V = 7;
E = 20;
G[0].push_back(edge(1, 2));
G[1].push_back(edge(0, 2));
G[0].push_back(edge(2, 5));
G[2].push_back(edge(0, 5));
G[1].push_back(edge(2, 4));
G[2].push_back(edge(1, 4));
G[1].push_back(edge(3, 6));
G[3].push_back(edge(1, 6));
G[1].push_back(edge(4, 10));
G[4].push_back(edge(1, 10));
G[2].push_back(edge(3, 2));
G[3].push_back(edge(2, 2));
G[3].push_back(edge(5, 1));
G[5].push_back(edge(3, 1));
G[4].push_back(edge(5, 3));
G[5].push_back(edge(4, 3));
G[4].push_back(edge(6, 5));
G[6].push_back(edge(4, 5));
G[5].push_back(edge(6, 9));
G[6].push_back(edge(5, 9));
// 测试数据结束
dijkstra(0);
cout << d[V - 1] << endl;
auto path = get_path(V - 1);
copy(path.begin(), path.end(), ostream_iterator<int>(cout, " "));
cout << endl;
system("pause");
return 0;
}
///////////////////////////End Sub//////////////////////////////////
输出:
16 0 2 3 5 4 6 请按任意键继续. . .
任意两点之间的距离Floyd-Warshall算法
d[u][v]表示边e=(u,v)的权值,不存在的时候等于无穷大或者d[i][i] = 0
在只是用顶点0~k和i,j的情况下,记i到j的最短路径长度为d[k+1][i][j],如果这次不经过顶点k那么d[k][i][j] = d[k-1][i][j],否则d[k][i][j] = d[k-1][i][k] + d[k-1][k][j],两种情况取小值作为d的值。注意到第一个下标k是冗余的,于是忽略它。
Floyd-Warshall算法三重循环,实现起来非常简单。复杂度于是成为O(|V|3);可以处理负边,如果需要判断图中是否存在负圈,只需检查d中是否有负数即可。
#include <iostream>
#include <algorithm>
using namespace std;
#define MAX_V 1024
int d[MAX_V][MAX_V]; // d[u][v]表示边e=(u,v)的权值,不存在的时候等于无穷大或者d[i][i] = 0
int V; // 顶点数
void warshall_floyd()
{
for (int k = 0; k < V; ++k)
{
for (int i = 0; i < V; ++i)
{
for (int j = 0; j < V; ++j)
{
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
}
}
}
///////////////////////////SubMain//////////////////////////////////
int main(int argc, char *argv[])
{
// 测试数据
V = 7;
memset(d, 0x3f, V * MAX_V * sizeof(int));
d[0][1] = 2;
d[0][2] = 5;
d[1][2] = 4;
d[1][3] = 6;
d[1][4] = 10;
d[2][3] = 2;
d[3][5] = 1;
d[4][5] = 3;
d[4][6] = 5;
d[5][6] = 9;
// 无向图必须重复一遍
d[1][0] = 2;
d[2][0] = 5;
d[2][1] = 4;
d[3][1] = 6;
d[4][1] = 10;
d[3][2] = 2;
d[5][3] = 1;
d[5][4] = 3;
d[6][4] = 5;
d[6][5] = 9;
warshall_floyd();
cout << d[0][V - 1] << endl;
system("pause");
return 0;
}
///////////////////////////End Sub//////////////////////////////////
码农场