给定结点K和一个有向图edges,我们需要求出K结点到所有结点的最短路径,采用Dijkstra算法,核心思想是贪心,其算法步骤主要为以下:
- 将结点分为两类[未确定结点]以及[已确定结点],需要从当前全部[未确定结点]中,找到距离K结点最短的点x
- 通过点x更新其他所有点距离源点的最短距离,更新的含义是:用起点到某个点y的距离,和起点到x的最短距离加上x到y的距离,更新为两者中的最小值
- 当全部其他点都遍历完成后,一次循环结束,将x标记为[已确定结点],进入下一轮循环,直到所有点都被标记上。
实现的一些细节如下:
- Dijkstra算法需要存储各个边的边权,稠密图可以采用邻接矩阵来存储,edges[i][j]表示从点i到点j的距离,若两点之间没有边,则初始化为inf,inf的值设置为INT_MAX/2,是因为在更新最短路的时候,防止两个距离相加溢出。
- 需要记录所有点到源点K的最短距离,使用dist数组来记录,dist[i]表示i结点到源点K的最短距离,源点到源点的距离为0
- 算法需要标记某一结点是否被标记为[已确定结点],因此用vis数组来标记,当其值为true时说明此节点已经被更新过,已经确定了最短路。
借助题目网络延迟时间,其代码如下:
int networkDelayTime(vector<vector<int>>& times, int n, int k) {
const int inf=INT_MAX/2;
vector<vector<int>>edges(n,vector<int>(n,inf)); //邻接矩阵
for(auto& t:times){ //结点从1到n,为了方便,改成0~n-1
int x=t[0]-1; //起始结点
int y=t[1]-1; //终点
edges[x][y]=t[2]; //距离
}
vector<int>dist(n,inf); //距离数组,初始化为inf
dist[k-1]=0; //K结点到K结点的距离为0
vector<bool>vis(n,0); //初始所有结点都未被访问
for(int i=0;i<n;i++){
//在[未确定结点]中找到距离最短的结点
int x=-1;
for(int y=0;y<n;y++){
if(!vis[y]&&(x==-1||dist[y]<dist[x])){
x=y;
}
}
//用该点更新到其他所有点的距离
vis[x]=true;
for(int y=0;y<n;y++){
dist[y]=min(dist[y],dist[x]+edges[x][y]);
}
}
int ans=*max_element(dist.begin(),dist.end()); //找到距离最短的点
return ans==inf? -1:ans;
}
除了枚举,我们还可以使用一个小根堆来寻找「未确定节点」中与起点距离最近的点。
int networkDelayTime(vector<vector<int>> ×, int n, int k) {
const int inf = INT_MAX / 2;
vector<vector<pair<int, int>>> g(n);
for (auto &t : times) {
int x = t[0] - 1, y = t[1] - 1;
g[x].emplace_back(y, t[2]);
}
vector<int> dist(n, inf);
dist[k - 1] = 0;
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> q;
q.emplace(0, k - 1);
while (!q.empty()) {
auto p = q.top();
q.pop();
int time = p.first, x = p.second;
if (dist[x] < time) { //dist[x]可能已经经过松弛变小了,不必入队列
continue;
}
for (auto &e : g[x]) { //利用最短边进行松弛
int y = e.first, d = dist[x] + e.second;
if (d < dist[y]) {
dist[y] = d;
q.emplace(d, y);
}
}
}
int ans = *max_element(dist.begin(), dist.end());
return ans == inf ? -1 : ans;
}