Dijkstra法解单源最短路径问题(附手写堆代码)
原题来自洛谷【模板】单源最短路径(标准版)
题目描述
给定一个 n n n 个点, m m m 条有向边的带非负权图,请你计算从 s s s 出发,到每个点的距离。
数据保证你能从 s s s 出发到任意点。
输入格式
第一行为三个正整数 n , m , s n,m,s n,m,s 。 第二行起 m m m 行,每行三个非负整数 ui,vi,wi, 表示从 ui到 vi 有一条权值为 wi 的有向边。
输出格式
输出一行 n n n 个空格分隔的非负整数,表示 s s s 到每个点的最短距离。
输入输出样例
//输入
4 6 1
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4
//输出
0 2 4 3
Dijkstra算法
简要原理:Dijkstra算法的本质思想为贪心,同时只适用于不含负权边的图,原理为每次找到一个dis最小的点,然后用其去更新周围的点,直到所有的点都被更新即结束。
主要过程:①首先初始化dis[s]为0 (s为出发点),其余点的dis值设为无穷大;②由已给权值更新s到其余点的dis值;③找出目前未标记的点中的dis的最小值的点x(s一开始就已经被标记),易得此距离即为s到x的最近距离,由x更新到其余未标记点的dis值,更新完后标记x;④反复进行上述步骤,直到所有点都被标记,即遍历完成。
图解(图源自《算法导论》)
优化:通过观察发现有一个步骤是取出dis最小的点,于是这里就可以通过堆优化来优化时间。
代码部分(非手写堆)
#include<bits/stdc++.h>
#define maxn 100500
using namespace std;
struct edge
{
int to, dis, next;
}e[maxn];
int head[maxn], dis[maxn], cnt = 0;
bool vis[maxn];//标记数组
int n, m, s;
void add(int u, int v, int d){ // 点加入图
cnt++;
e[cnt].to = v;
e[cnt].dis = d;
e[cnt].next = head[u];
head[u] = cnt;
}
struct node
{
int dis;
int pos;
bool operator <( const node &x )const
{
return x.dis < dis;
}
};
std::priority_queue<node> q; // 创建优先队列
void dijkstra(){
for(int i = 1; i <= n; i++)
dis[i] = 0x7fffffff;
dis[s] = 0;
q.push((node){0, s}); //出发点入队
while(!q.empty()){
node tmp = q.top();
q.pop();
int pos = tmp.pos, d = tmp.dis;
if(vis[pos])
continue;
vis[pos] = 1;
for(int i = head[pos]; i; i = e[i].next){ //遍历与队首点连接的结点
int y = e[i].to;
if(dis[y] > dis[pos] + e[i].dis){
dis[y] = dis[pos] + e[i].dis; //更新dis值
if(!vis[y])
q.push(( node ){dis[y], y}); //该点及其dis值入队
}
}
}
}
int main(){
int u, v, d;
scanf("%d%d%d", &n, &m, &s);
for(int i = 0; i < m; i++){
scanf("%d%d%d", &u, &v, &d);
add(u, v, d);
}
dijkstra();
for(int i = 1; i <= n; i++)
printf("%d ", dis[i]);
return 0;
}
代码部分(手写堆)
#include<stdio.h>
#include<string.h>
#include<math.h>
#define maxn 1000005
#define inf 2e9
int n, m, s, cnt = 0, count = 0;
struct edge{
int next, to, dis;
}e[maxn];
int head[maxn], dis[maxn], vis[maxn];
struct node{ //定义优先队列元素
int pos;
int d;
}a[maxn], tmp;
int read(){ //读入函数
int x = 0;
char ch = getchar();
while(ch < '0' || '9' < ch)
ch=getchar();
while('0' <= ch && ch <= '9')
x = (x << 3) + (x << 1) + (ch ^ '0'), ch = getchar();
return x;
}
void add(int u, int v, int d){
cnt += 1;
e[cnt].dis = d;
e[cnt].to = v;
e[cnt].next = head[u];
head[u] = cnt;
}
void push(struct node x){ // 入队
int i, j;
count += 1;
a[count] = x;
for(i = count, j = count >> 1; j; i = j, j >>= 1){
if(a[i].d < a[j].d)
tmp = a[i], a[i] = a[j], a[j] = tmp;
else
break;
}
return;
}
void pop(){ //出队
int i, j;
a[1] = a[count--];
for(i = 1, j = 2; j <= count; i = j, j <<= 1){
if(a[j + 1].d < a[j].d)
j += 1;
if(a[i].d > a[j].d)
tmp = a[i], a[i] = a[j], a[j] = tmp;
else
break;
}
return;
}
void dijkstra(){
int i, j;
struct node x;
for(i = 1; i <= n; i++)
dis[i] = inf;
dis[s] = 0;
push((struct node){s, 0});
while(count){
x = a[1];
pop();
if(vis[x.pos])
continue;
vis[x.pos] = 1;
for(i = head[x.pos]; i; i = e[i].next){
if(dis[e[i].to] > dis[x.pos] + e[i].dis){ //权值更新
dis[e[i].to] = dis[x.pos] + e[i].dis;
if(!vis[e[i].to]){
push((struct node){e[i].to, dis[e[i].to]});
}
}
}
}
}
int main() {
int i, j, k, t;
int u, v, d;
n = read(), m = read(), s = read();
for(i = 1; i <= m; i++){
u = read(), v = read(), d = read();
add(u, v, d);
}
dijkstra();
for(i = 1;i <= n; ++i)
printf("%d ",dis[i]);
return 0;
}