在加权图G=(V,E)中,求给定顶点s,d之间各边权值总和最小的路径,这就是最短路径问题。
这个问题主要分为两类:
- 单源最短路径:在图G中,求给定顶点s到其他所有顶点di之间的最短路径
- 全点对间最短路径:在图G中,求“每一对顶点”之间的最短路径
求单源最短路径,其实就是求从起点出发的最短路径生成树的过程。如果顶点s到G的所有顶点都存在路径,那么一定存在一棵以s为根,包含s到G所有顶点最短路径的生成树T。这种树就称为最短路径生成树。
狄克斯特拉算法
解决最短路径生成树问题,就需要用到狄克斯特拉算法。
简单版本的狄克斯特拉算法就是这样的:
设图G=(V,E)所有顶点的集合为V,起点为s,最短路径生成树中包含的顶点集合为S。在各计算步骤中,我们将选出最短路径生成树的边和顶点,并将其添加到S。
对于各顶点i,设仅经由S内的顶点s到i的最短路径成本为d[i],i在最短路径中的父节点为p[i]
- 初始状态下,将S置空。
- 初始化s的d[s]=0,除此以外的d[i]=∞
- 循环进行下述处理,直到S=V为止
- 从V-S中选出d[u]最小的顶点u
- 将u添加到S,同时将与u相邻且属于V-S的所有顶点v的值按照下述方式更新
- if(d[u]+w(u,v)<d[v])
- d[v] = d[u] + w(u,v)
- p[v]=u
- if(d[u]+w(u,v)<d[v])
当上述处理结束后,d数组中记录的就是所有节点到起点的最短路径。
要注意的是,狄克斯特拉算法不能应用于包含负权值的图,具有负权值的图可以使用贝尔-福特算法或者弗洛伊德算法来处理。
题目来自于ALDS1_12_B
输入数据是邻接表的形式表示的,但是我们依然可以使用邻接矩阵的方法来存储他们,毕竟空间还是够的,n的最大值是100
#include <iostream>
#include <string.h>
using namespace std;
#define MAXN 105
#define WHITE 0
#define GRAY 1
#define BLACK 2
const int inf = (1 << 21);
int M[MAXN][MAXN];
int d[MAXN]; //节点的最小权值和
int p[MAXN]; //最短路径生成树上的节点的父节点
int color[MAXN] = {0}; //节点的访问状态
int n;
void dijkstra() //狄克斯特拉算法
{
d[0] = 0;
color[0] = GRAY;
int min_cost;
while (true)
{
min_cost = inf;
int u = -1;
for (int i = 0; i < n; ++i) //找当前最小路径生成树以外的,权值最小的边,往那边扩展
{
if (color[i] != BLACK && d[i] < min_cost)
{
min_cost = d[i];
u = i;
}
}
if (u == -1)
break;
color[u] = BLACK; //当前节点加入最小路径生成树,标记为黑色
for (int v = 0; v < n; ++v) //对于加入节点u之后的最小路径生成树,将待选节点的权值和算出来。以便下一轮扩展。
{
if (color[v] != BLACK && M[u][v] != inf)
{
if (d[u] + M[u][v] < d[v])
{
d[v] = d[u] + M[u][v];
p[v] = u;
color[v] = GRAY;
}
}
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
M[i][j] = inf;
for (int i = 0; i < n; ++i)
d[i] = inf;
for (int i = 0; i < n; ++i)
{
int u, k;
cin >> u >> k;
int v, c;
for (int j = 0; j < k; ++j)
{
cin >> v >> c;
M[u][v] = c;
}
}
dijkstra();
for (int i = 0; i < n; ++i)
{
cout << i << ' ' << d[i] << endl;
}
}
上面这样子计算单源最短路径,时间复杂度为O(n2),然后空间复杂度也是n2的。
然后对于 ALDS1_12_C 这道题目,n的范围被扩大到了10000,算一下之后会发现,如果采用上面的算法进行处理的话,一定会超时。这就需要我们降低时间复杂度
要降低空间复杂度的话,可以用邻接表或者链式前向星结合vector来解决,
那么,如何降低时间复杂度呢?
这就需要用到优先级队列。
这里我们用到的优先级队列需要是小根堆。在应用STL的priority_queue之后,剩下的部分其实思路是类似的。直接看代码就好了。
#include <iostream>
#include <queue>
#include <algorithm>
#include <vector>
using namespace std;
#define MAXN 10005
#define INF (1 << 21)
#define WHITE 0
#define GRAY 1
#define BLACK 2
int n;
vector<pair<int, int>> adj[MAXN]; //加权有向图的邻接表表示法
int color[MAXN] = {0};
int d[MAXN];
int p[MAXN]; //最短路径生成树(存储每个节点的父节点)
void dijkstra()
{
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq; //这里面第一个int指的是路径长度,第二个int指的是节点编号
for (int i = 0; i < n; ++i)
d[i] = INF;
d[0] = 0;
pq.push(make_pair(0, 0));
color[0] = GRAY;
while (!pq.empty())
{
pair<int, int> f = pq.top();
pq.pop();
int u = f.second;
color[u] = BLACK;
//取出最小值,如果不是最短路径则忽略
if (d[u] < f.first)
continue;
for (int j = 0; j < adj[u].size(); ++j)
{
int v = adj[u][j].first;
if (color[v] == BLACK)
continue;
if (d[v] > d[u] + adj[u][j].second)
{
d[v] = d[u] + adj[u][j].second;
p[v] = u; //建立最短路径生成树
pq.push(make_pair(d[v], v));
color[v] = GRAY;
}
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
int u, k, v, c;
for (int i = 0; i < n; ++i)
{
cin >> u >> k;
for (int j = 0; j < k; ++j)
{
cin >> v >> c;
adj[u].push_back(make_pair(v, c));
}
}
dijkstra();
for (int i = 0; i < n; ++i)
{
cout << i << ' ' << d[i] << endl;
}
}
分析知,从优先级队列中取出顶点u的时间复杂度为O(|V|log|V|),更新d[v]的时间复杂度为O(|E|log|V|),因此,整体的时间复杂度就是O((|E|+|V|)log|V|),那么掐指一算,是可以在1s内完成的。
转载请注明原文出处:https://www.longjin666.top/?p=746
欢迎关注我的公众号“灯珑”,让我们一起了解更多的事物~