给定n个结点两两之间的单向边的长度,求两两之间的最短路径。
输入第一行包含一个整数n,表示点数。
接下来n行,每行包含n个整数,第i行表示第i个点到每个点的边的长度,如果没有边,则用0表示。
输出n行,第i行表示第i个点到其他点的最短路径长度,如果没有可达的路径,则输出-1。
3
0 1 0
0 0 6
0 2 0
0 1 7
-1 0 6
-1 2 0
1<=n<=1000,0<边长<=10000。
本题是一个求最短路径的问题,而且是带权、多源的最短路径。
即:两个点之间的边是有长度的。如果是不带权的边,则求最短路径只要最基础的BFS即可,而带权则需要特殊的算法
本题要求各个点到其余点的最短路径,因此是一个多源的,源就是路径的起点。
迪杰斯特拉算法是一个经典的利用以求得的最短路径更新其余最短路径的方式,但是它只能解决单源最短路径的问题,我们直接看代码:
vector<int> dijkstra(int src, vector<vector<Node> >& g)
{
vector<int> d(n, inf); // 源点src到其余各点的最短距离
d[src] = 0; // src到src的距离为0
// 采用小根堆优化dijkstra算法,优先遍历权值较小的节点(贪心)
// 记录{上一次从src到cur节点的最短距离, cur}
priority_queue<pii, vector<pii>, greater<pii> > q;
q.push(make_pair(0, src));
while (!q.empty())
{
pii u = q.top();
q.pop();
// cur的最短距离已经更新过了,此时不再使用该旧值
if (d[u.second] < u.first)
{
continue;
}
// 遍历u可以到达的点v,并更新src到v的最短路径
for (int i = 0; i < g[u.second].size(); ++i)
{
Node v = g[u.second][i];
if (d[u.second] + v.len < d[v.num])
{
d[v.num] = d[u.second] + v.len;
q.push(make_pair(d[v.num], v.num));
}
}
}
return d;
}
首先将{0, src}存入优先队列,然后遍历队列中的节点。
对于遍历到的节点{length, u}, 我们首先判断length是否等于d[u]。
因为src到u的距离是一直在变化的,而在上一次存入节点{length,u}到这一次取出的时间段内,d[u]可能已经变得小于length了,那么我们就不必再用这个length去更新其它节点(因为本轮更新必然不会使d数组发生变化)
此外,我们将节点按照length从小到大存放到优先队列中,每次取出较小的length来更新其余结点的d,这是一种贪心的思想。
先上代码感受一下:
for (int k = 0; k < n; ++k)
{
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
dis[i][j] = min(dis[i][k] + dis[k][j], dis[i][j]);
}
}
}
简单来说,floyd算法本质就是dp思想,是一个不断利用已有值更新的过程。
我们用dis[i][j]表示i到j的最短路径
遍历其余节点k,检查dis[i][k]+dis[k][j]是否小于dis[i][j]
如果是,则更新dis[i][j] = dis[i][k]+dis[k][j]
值得注意的是,floyd算法需要使用邻接表的形式存储图,对于无边的两个点,其权值设为inf。
此外,floyd算法的三层循环中,外层循环是k,也就是说,我们先固定中转站k,然后更新i在经过k后到达j的最小距离。
floyd算法
int n, x;
typedef pair<int, int> pii;
const int inf = 0x3f3f3f3f;
int main()
{
cin >> n;
vector<vector<int> > dis(n, vector<int>(n));
// 建图
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
scanf("%d", &x);
if (x)
{
dis[i][j] = x;
}
else // i、j没有边则令边长为inf
{
dis[i][j] = inf;
}
}
dis[i][i] = 0; // 自己到自己,边长为0
}
for (int k = 0; k < n; ++k)
{
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
dis[i][j] = min(dis[i][k] + dis[k][j], dis[i][j]);
}
}
}
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
if (dis[i][j] != inf)
{
cout << dis[i][j] << " ";
}
else
{
cout << -1 << " ";
}
}
cout << endl;
}
return 0;
}
dijkstra算法
int n, x;
typedef pair<int, int> pii;
const int inf = 0x3f3f3f3f;
struct Node
{
int num;
int len;
Node(int n, int l)
{
num = n;
len = l;
}
};
void dijkstra(int src, vector<vector<Node> >& g)
{
vector<int> d(n, inf); // 源点src到其余各点的最短距离
d[src] = 0; // src到src的距离为0
// 采用小根堆优化dijkstra算法,优先遍历权值较小的节点(贪心)
priority_queue<pii, vector<pii>, greater<pii> > q; // 记录{上一次从src到cur节点的最短距离, cur}
q.push(make_pair(0, src));
while (!q.empty())
{
pii u = q.top();
q.pop();
// cur的最短距离已经更新过了,此时不再使用该旧值
if (d[u.second] != u.first)
{
continue;
}
// 遍历u可以到达的点v
for (int i = 0; i < g[u.second].size(); ++i)
{
Node v = g[u.second][i];
if (d[u.second] + v.len < d[v.num])
{
d[v.num] = d[u.second] + v.len;
q.push(make_pair(d[v.num], v.num));
}
}
}
for (int i = 0; i < n; ++i)
{
if (d[i] == inf)
{
cout << -1 << " ";
}
else
{
cout << d[i] << " ";
}
}
cout << endl;
}
int main()
{
cin >> n;
vector<vector<Node> > g(n);
// 建图
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
scanf("%d", &x);
if (x)
{
g[i].push_back(Node(j, x));
}
}
}
// 方法一:对n个结点进行dijkstra算法,算出它到其它点的最短路径(适用于稀疏图)
for (int i = 0; i < n; ++i)
{
dijkstra(i, g);
}
return 0;
}
dijkstra算法是确定一个源src,然后获取从src到其余各点的最短路径长度。因此对于本题而言,我们需要使用n次dijkstra。
而floyd算法非常直截了当地通过三层for循环达到目的。
二者的时间复杂度都是O(n^3)。