Dijkstra算法的线段树优化

目录

Dijkstra算法

算法简介

实现方法

利用线段树的优化

时间复杂度


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)

NOIP 2018 RP++
原文地址:https://www.cnblogs.com/Black-S/p/9930717.html