Dijkstra(题目练习解析)
点击 -> Dijkstra算法详细讲解
王国
题目描述
小明是王国的王子,今天是他登基之日。在即将成为国王之前,老国王给他出了道题,他想要考验小明是否有能力管理国家。
题目的内容如下:
蓝桥王国一共有 N 个建筑和 M 条单向道路,每条道路都连接着两个建筑,每个建筑都有自己编号,分别为 1∼N 。(其中皇宫的编号为 1)
国王想让小明回答从皇宫到每个建筑的最短路径是多少,但紧张的小明此时已经无法思考,请你编写程序帮助小明回答国王的考核。
输入描述
输入第一行包含三个正整数 N,M。
第 22 到 M + 1 行每行包含三个正整数 u,v,w,表示 u→v 之间存在一条距离为 w 的路。
1 ≤ N ≤ 3×10^5,1 ≤ m ≤ 10^6,1 ≤ ui,vi ≤ N,0 ≤ wi ≤ 10^9。
输出描述
输出仅一行,共 N 个数,分别表示从皇宫到编号为 1∼N 建筑的最短距离,两两之间用空格隔开。(如果无法到达则输出 -1)
样例输入
3 3
1 2 1
1 3 5
2 3 2
样例输出
0 1 3
参考代码:
“邻接表 + 优先队列”
- 用邻接表存图和查找邻居。对邻居的查找和扩展,是通过动态数组
vector <edge> e[NUM]
实现的邻接表,和 SPFA 一样。其中 e[i] 存储第 i 个结点上所有的边,边的一头是它的邻居,即struct edge
的参数 to。需要扩展结点 i 的邻居的时候,查找 e[i] 即可。已经放到集合 A 中的结点,不要扩展;程序中用bool done[NUM]
记录集合 A,当done[i] = true
时,表示它在集合 A 中,已经找到了最短路。- 在集合 B 中找距离起点最短的结点。直接用 STL 的优先队列实现,程序中是
priority_queue <s_node> Q
。但是有关丢弃的动作,STL的优先队列无法做到。例如在 “Dijkstra执行过程”的步骤 3 中,需要在 B={(2-5), (2-4), (4,7)} 中丢弃 (2- 5),但是 STL 没有这种操作。在程序中也是用bool done[NUM]
协助解决这个问题。从优先队列 pop 出 (2-4)时,记录done[2] = true
,表示结点 2 已经处理好。下次从优先队列 pop 出 (2-5)时,判断 done[2] 是 true,丢弃。- Dijkstra 打印最短路径非常容易。定义 pre[] 记录前驱结点,然后用
print_path()
打印整个路径。
#include<bits/stdc++.h>
using namespace std;
const long long INF = 0x3f3f3f3f3f3f3f3fLL; //这样定义INF的好处是: INF <= INF+x
const int NUM = 3e5+2;
struct edge{
int from, to; long long w;
//边:起点,终点,权值。起点from并没有用到,e[i]的i就是from
edge(int a, int b,long long c){from=a; to=b; w=c;}
};
vector<edge>e[NUM]; //用于存储图
struct s_node{
int id; long long n_dis; //id:结点;n_dis:这个结点到起点的距离
s_node(int b,long long c){id=b; n_dis=c;}
bool operator < (const s_node & a) const
{ return n_dis > a.n_dis;}
};
int n,m;
int pre[NUM]; //记录前驱结点
void print_path(int s, int t) { //打印从s到t的最短路
if(s==t){ printf("%d ", s); return; } //打印起点
print_path(s, pre[t]); //先打印前一个点
printf("%d ", t); //后打印当前点。最后打印的是终点t
}
long long dis[NUM]; //记录所有结点到起点的距离
void dijkstra(){
int s = 1; //起点s是1
bool done[NUM]; //done[i]=true表示到结点i的最短路径已经找到
for (int i=1;i<=n;i++) {dis[i]=INF; done[i]=false; } //初始化
dis[s]=0; //起点到自己的距离是0
priority_queue <s_node> Q; //优先队列,存结点信息
Q.push(s_node(s, dis[s])); //起点进队列
while (!Q.empty()) {
s_node u = Q.top(); //pop出距起点s距离最小的结点u
Q.pop();
if(done[u.id]) //丢弃已经找到最短路径的结点。即集合A中的结点
continue;
done[u.id]= true;
for (int i=0; i<e[u.id].size(); i++) { //检查结点u的所有邻居
edge y = e[u.id][i]; //u.id的第i个邻居是y.to
if(done[y.to]) //丢弃已经找到最短路径的邻居结点
continue;
if (dis[y.to] > y.w + u.n_dis) {
dis[y.to] = y.w + u.n_dis;
Q.push(s_node(y.to, dis[y.to]));
//扩展新的邻居,放到优先队列中
pre[y.to]=u.id; //如果有需要,记录路径
}
}
}
// print_path(s,n); //如果有需要,打印路径: 起点1,终点n
}
int main(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
e[i].clear();
while (m--) {
int u,v,w;
scanf("%d%d%lld",&u,&v,&w);
e[u].push_back(edge(u,v,w));
// e[v].push_back(edge(v,u,w)); //本题是单向道路
}
dijkstra();
for(int i=1;i<=n;i++){
if(dis[i]>=INF) cout<<"-1 ";
else printf("%lld ", dis[i]);
}
return 0;
}