目录
Dijkstra算法
算法简介
Dijkstra算法是由荷兰计算机科学家Dijkstra于1959年提出的,因此又叫Dijkstra算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有向图中最短路径问题。Dijkstra算法主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。
(来自百度百科)
实现方法
使用dis数组记录目标到各结点的距离。
通过查询dis数组中的最小值更新其他路径,这个操作我们称作松弛。
dis[j] = min(dis[j],dis[i] + edge[i][j])
由上面的转换我们可以知道当前dis数组中的最小值无法被其他值松弛,
所以我们就可以利用这个最小值来松弛其他边,然后对它做一个标记。
那么使用flag数组记录已经无法被更新最短路的点。
(下面是未加优化的Dijkstra算法)
#include<iostream>
using namespace std;
const int edge_size = 100050;
const int node_size = 10050;
const int inf = 1e9;
int n,m,goal;
int dis[node_size];
bool flag[node_size];
int first_edge[node_size];
struct edge {
int val,to,next;
edge() {
val = to = next = 0;
}
edge(int a,int b,int c) {
val = a;
to = b;
next = c;
}
} data[edge_size];//邻接表存图
void add_edge(int from,int to,int val,int i) {
data[i] = edge(val,to,first_edge[from]);
first_edge[from] = i;
return;
}
int MIN(int a,int b) {
return a < b ? a : b;
}
void Dijkstra() {
for(int i = 0;i <= n;i++)//初始化
dis[i] = inf;
for(int i = first_edge[goal];i;i = data[i].next)
dis[data[i].to] = MIN(dis[data[i].to],data[i].val);//同一点对间有多条边的情况
dis[goal] = 0;
flag[goal] = true;
for(int i = 1;i <= n - 1;i++) {
int Min = 0;
for(int i = 1;i <= n;i++) {//寻找最小dis
if(flag[i]) continue;
else if(dis[i] < dis[Min])
Min = i;
}
flag[Min] = true;
for(int i = first_edge[Min];i;i = data[i].next)
if(flag[data[i].to]) continue;
else dis[data[i].to] = MIN(dis[data[i].to],dis[Min] + data[i].val);//松弛操作
}
}
void output() {//输出答案
for(int i = 1;i <= n;i++)
cout << dis[i] << ' ';
cout << endl;
return;
}
int main() {
cin >> n >> m >> goal;
for(int i = 1;i <= m;i++) {
int a,b,c;
cin >> a >> b >> c;
add_edge(a,b,c,i);
}
Dijkstra();
output();
return 0;
}
利用线段树的优化
在代码中我们可以看到,每次在寻找最小dis的时候,存在着许多的无用操作;
寻找最小dis这一行为可以转化为RMQ问题,那么我们就可以使用线段树来优化。
(直接上代码)
#include<iostream>
#include<cstring>
using namespace std;
const int edge_size = 100050;
const int node_size = 10050;
const int inf = 1e9;
int n,m,goal;
int dis[node_size];
bool flag[node_size];
int first_edge[node_size];
struct edge {
int val,to,next;
edge() {
val = to = next = 0;
}
edge(int a,int b,int c) {
val = a;
to = b;
next = c;
}
} data[edge_size];//邻接表存图
void add_edge(int from,int to,int val,int i) {
data[i] = edge(val,to,first_edge[from]);
first_edge[from] = i;
return;
}
int MIN(int a,int b) {
return a < b ? a : b;
}
int tree[node_size * 4],Dis[node_size];//建立一个假的'dis'数组便于维护线段树
void update(int pos) {//线段树的更新操作
int father = pos / 2;
while(father >= 1) {
int lson = father * 2;
int rson = father * 2 + 1;
if(Dis[tree[lson]] > Dis[tree[rson]])
tree[father] = tree[rson];
else tree[father] = tree[lson];
father /= 2;
}
}
void Dijkstra() {
memset(tree,0,sizeof(tree));
for(int i = 0;i <= n;i++)//初始化
Dis[i] = dis[i] = inf;
for(int i = first_edge[goal];i;i = data[i].next)
Dis[data[i].to] = dis[data[i].to] = MIN(dis[data[i].to],data[i].val);
//同一点对间有多条边的情况
Dis[goal] = dis[goal] = 0;
int leaf_start;//便于查找各结点在线段树中的相应位置
for(int i = 0;;i++) {
if((1 << i) > n) {
leaf_start = (1 << i) - 1;
break;
}
}
for(int i = 1;i <= n;i++) {
tree[leaf_start + i] = i;
update(leaf_start + i);
}
Dis[goal] = inf;//在这里我们将已经标记过的点对应的Dis设置为inf,这样便不会影响线段树的查询
flag[goal] = true;
update(leaf_start + goal);
for(int i = 1;i <= n - 1;i++) {
int Min = tree[1];
Dis[Min] = inf;
flag[Min] = true;
update(leaf_start + Min);
for(int i = first_edge[Min];i;i = data[i].next)
if(flag[data[i].to]) continue;
else {
Dis[data[i].to] = dis[data[i].to] =
MIN(dis[data[i].to],dis[Min] + data[i].val);//松弛操作
update(leaf_start + data[i].to);
}
}
}
void output() {//输出答案
for(int i = 1;i <= n;i++)
cout << dis[i] << ' ';
cout << endl;
return;
}
int main() {
cin >> n >> m >> goal;
for(int i = 1;i <= m;i++) {
int a,b,c;
cin >> a >> b >> c;
add_edge(a,b,c,i);
}
Dijkstra();
output();
return 0;
}
时间复杂度
线段树的每次更新操作复杂度是O(logn),一共有(m+n)次更新(也可以通过特判使更新次数变得更少)
所以使用后线段树总的时间复杂度为O((m+n)logn)