阅读堆优化代码之前,首先要弄懂dijkstra的基础思路,建议b站找个视频看下图解过程,二十分钟足矣。其中算法核心有以下几点:
- 将所有点分为已确定最短路径的点集 N v i s {N_{vis}} Nvis和未确定的点集 N u n v i s N_{unvis} Nunvis,以及一个 N v i s {N_{vis}} Nvis到 N u n v i s N_{unvis} Nunvis的边集(每一条边至少保存目标id以及到源点 S S S的距离)。
- 初始化:将源点S初始化到 N v i s {N_{vis}} Nvis中,并将
以上虽然是求概率最大路径,和通常求最短路径相反,但思路相同,重载比较运算符即可。
堆优化的最关键一点,是要明白堆顶(也就是代码中的优先队列top元素)如果没有被vis标记过,则一定是下一个到源点确定为最大概率的点。
struct Node {
double p; // 该结点到源点的当前最大概率
int node_id;
bool operator < (const Node &b) const {
return p < b.p;
} //使用重载小于运算,从而利用优先队列模板
};
class Solution {
public:
double maxProbability(int n, vector<vector<int>>& edges, vector<double>& succProb, int start, int end) {
vector<vector<Node>> g(n, vector<Node>{}); //邻接表存储
for(int i = 0; i < edges.size(); ++i) {
g[edges[i][0]].push_back(Node{succProb[i], edges[i][1]});
g[edges[i][1]].push_back(Node{succProb[i], edges[i][0]});
}
vector<bool> vis(n, false); //这个数组记录已经确定最大概率的node
priority_queue<Node> q;
q.push(Node{1., start}); //初始化源点
while (!q.empty()) {
Node t = q.top();
if (vis[t.node_id]) {q.pop(); continue;} //之前已经找到最大概率的node, 跳过这个点
if (t.node_id == end) break; //确定目标点最大概率,终止循环
vis[t.node_id] = true; //堆顶一定为下一个确定最大概率的点
q.pop();
for (Node &n: g[t.node_id]) {
if (vis[n.node_id]) continue;
double new_p = t.p * n.p;
if (new_p > 0) {
q.push(Node{new_p, n.node_id}); //优先队列中可能有多个node_id相同的Node,但这并不影响大局
}
}
}
if (q.empty()) //目标和原点不连通
return 0.;
else
return q.top().p;
}
};