题目来源
题目描述
class Solution {
public:
int networkDelayTime(vector<vector<int>>& times, int n, int k) {
}
};
题目解析
- 题目实际上是求节点K到其他所有点中最远的距离,那么首先需要求出节点K到其他所有点的最短路,然后取最大值即可。
Dijkstra 算法
- 单源最短路问题可以使用 Dijkstra 算法,其核心思路是贪心算法。流程如下:
- 首先,Dijkstra 算法需要从当前全部未确定最短路的点中,找到距离源点最短的点 x。
- 其次,通过点x更新其他所有点距离源点的最短距离。比如目前点A距离源点最短,距离为3;有一条边A->B的有向边,权值为1,那么源点先去A点再去B点距离为3+1=4
- 当全部其他点都遍历完成后,一次循环结束,将x标记为已经确定的最短路。进入下一轮循环,直到全部点被标记为确定了最短路。
- Dijkstra算法可以视为使用贪心策略优化后的广度优先搜索
我们通过一个[例子]对 Dijkstra 算法的流程深入了解一下:
以上图片为一个有向带权图,圆圈中为节点序号,箭头上为边权,右侧为所有点距离源点 0 的距离。
将顶点 0 进行标识,并作为点 xx,更新其到其他所有点的距离。一轮循环结束。
将顶点 2 进行标识,并作为新的点 x,更新。我们看到,原本点 1 的最短距离为 5,被更新为了 3。同理还更新了点 3 和点 4 的最短距离。
将顶点 1 进行标识,并作为新的点 xx,同样更新了点 4 到源点的最短距离。
再分别标识点 4 和点 3,循环结束。
我们来看实现时需要的代码支持:
class Solution {
public:
int networkDelayTime(vector<vector<int>>& times, int n, int k) {
const int inf = INT32_MAX / 2;
// 邻接矩阵存储边信息
std::vector<std::vector<int>> g(n, std::vector<int>(n, inf));
for(auto & t : times){
// 边序号从 0 开始
int x = t[0] - 1, y = t[1] -1;
g[x][y] = t[2];
}
// 从源点到某点的距离数组
std::vector<int> dist(n, inf);
dist[k - 1] = 0; // 由于从 k 开始,所以该点距离设为 0,也即源点
// 节点是否被更新数组
std::vector<bool> used(n);
for (int i = 0; i < n; ++i) {
// 在还未确定最短路的点中,寻找距离最小的点
int x = -1;
for (int y = 0; y < n; ++y) {
if(!used[y] && (x == -1 || dist[y] < dist[x])){
x = y;
}
}
// 用该点更新所有其他点的距离
used[x] = true;
for (int y = 0; y < n; ++y) {
dist[y] = std::min(dist[y], dist[x] + g[x][y]);
}
}
// 找到距离最远的点
int ans = *max_element(dist.begin(), dist.end());
return ans == inf ? -1 : ans;
}
};
用优先队列(堆实现)优化迪杰斯特拉算法
#define MAXVALUE 0x3f3f3f3f
class Solution {
public:
vector<unordered_map<int, int>> mp;
int networkDelayTime(vector<vector<int>>& times, int n, int k) {
// 建图 - 邻接表
mp.resize(n + 1);
for (auto& edg : times) {
mp[edg[0]][edg[1]] = edg[2];
}
// 记录结点最早收到信号的时间
vector<int> r(n + 1, MAXVALUE);
// 优先队列中存放 [收到信号时间,结点]
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> s;
s.emplace(pair(0, k));
while (!s.empty()) {
auto cur = s.top();
s.pop();
if (r[cur.second] != MAXVALUE) continue;
r[cur.second] = cur.first;
for (auto& edg : mp[cur.second]) {
if (r[edg.first] == MAXVALUE) {
s.emplace(pair(edg.second + cur.first, edg.first));
}
}
}
int minT = -1;
for (int i = 1; i <= n; ++i)
minT = max(minT, r[i]);
return minT == MAXVALUE ? -1 : minT;
}
};
深度优先搜索
简单递归的搜索一遍图,当然继续向下搜索的前提是遍历到某一结点的时间有所改善,不然没必要继续向下继续搜索了,不过其实 DFS 对于求最短路并不合适
#define MAXVALUE 0x3f3f3f3f
class Solution {
public:
vector<unordered_map<int, int>> mp;
int networkDelayTime(vector<vector<int>>& times, int n, int k) {
// 建图 - 邻接表
mp.resize(n + 1);
for (auto& edg : times) {
mp[edg[0]][edg[1]] = edg[2];
}
// 记录结点最早收到信号的时间
vector<int> r(n + 1, MAXVALUE);
dfs(r, k, 0);
int minT = -1;
for (int i = 1; i <= n; ++i)
minT = max(minT, r[i]);
return minT == MAXVALUE ? -1 : minT;
}
void dfs(vector<int>& r, int i, int t) {
if (r[i] > t) {
r[i] = t;
for (auto& cur : mp[i]) {
dfs(r, cur.first, cur.second + t);
}
}
}
};
广度优先搜索
涉及最大最小的问题 BFS 显然比 DFS 更合适
class Solution {
const int inf = INT32_MAX / 2;
std::vector<std::unordered_map<int, int>> mp;
struct Info{
int node;
int time;
Info(int n, int t) : node(n), time(t){
}
};
public:
int networkDelayTime(vector<vector<int>>& times, int n, int k) {
// 建图 - 邻接表
mp.resize(n + 1);
for(auto &edg : times){
mp[edg[0]][edg[1]] = edg[2];
}
// 记录结点最早收到信号的时间
std::vector<int> dist(n + 1, inf);
dist[k] = 0;
// 队列中存放 [结点,收到信号时间]
queue<Info> s;
s.emplace(Info{k, 0});
while (!s.empty()){
auto cur = s.front();
s.pop();
for(auto &edg : mp[cur.node]){
int time = edg.second + cur.time;
// 仅当结点收到时间比记录时间更早才更新并入队
if(time < dist[edg.first]){
dist[edg.first] = time;
s.emplace(Info(edg.first, time));
}
}
}
// 找到距离最远的点
int ans = *max_element(dist.begin(), dist.end());
return ans == inf ? -1 : ans;
}
};