dijkstra算法是一种最短路径算法
用于计算单源最短路径,即从一个源点出发,到图中其他所有点的最短路径
要求是所有边的权值都为正
过程
令源点为s
-
建立两个数组dis,vis。dis[i]表示从源点出发到编号为i的点的距离 (初始时dis[s]=0,其他所有点的dis值为无穷大,在计算过程中,如果找到一条到达点 i 的更短的路径,dis[i]将更新为这条更短路径的距离) vis[i]表示 i 点的dis[i] 是否为真正的最短路径(vis[i]==true 表示 i 点的dis值已经确定是最小值,不能更短了)初始时所有点的vis值都为false。
-
每一轮中挑选出所有 vis 值为false的点中dis值最小的那一个,令为点 u ,这时可以保证 u 点的dis值为最短的路径,将其vis值更改为true。
-
更新点 u 周围的点的dis值,规则是:如果存在一条从 u 到 v 的权值为 w 的边,则我们可以找到一条路径从源点到u点,再经过这条边到达 v 点的路径,且其长度为dis[u]+w,如果比现在的dis[v]小的话,那么就找了一条更短的路径,更新dis[v]的值
-
重复过程2、3,直至所有点的vis值都为true。
证明
简单的口述证明一下 (懂的同学也看看,有些名词后面要用)
首先我们可以将图中的点分为三类,
- vis为true的点,称为“内部点”
- vis为false,但是dis!=正无穷的点 称为边界点
- dis==正无穷的点,称为“外部点”
显然,内部点不可能与外界点相连(相连指从内部点到外部点的边)
实际上,上述过程2中每次挑选的点 u 都是边界点,因为外部点的dis值为无穷大
到达点 u 的真正最短路径首先必经过一些内部点(点 u 为源点除外) ,如果经过了边界点 k,那么这条路的长度必然大于dis[k](因为所有边的权值都大于零,路径只可能越走越长),而dis[u]是所有边界点中最短的,即dis[u]<=dis[k] 所以到达 u 的最短路径不可能经过其他边界点。
其实上述证明不够严谨,因为dis[k]不一定是最短路径,可能之后dis[k] 可能更新为更小的值,但是更新后的值也一定大于dis[u] 因为每次跟更新都是由边界点向外更新(之后才把边界点纳入内部点)而dis[u]已经是边界点中最小的了,之后更新的值也肯定比dis[u]更大。
堆优化
我们已经知道原理,接下来要用算法实现,其中最重要的就是,怎么才能在步骤2中快速挑选出边界点中dis值最小的那个,堆正适合这项工作(不懂的同学先去别的地方看看堆是什么)既能在O(1)时间内找出最小值,又能在O(logn)时间将被更新的点的dis值加入到堆中排序。
代码
首先贴一下我的宏
#define fors(x,n) for(int x = 0,end = n; x < end; x++)
再贴一下我存图的代码
#define MAX_NODE 1000000+20
#define MAX_EDGE 10000000+20
int edge_cnt = 0;
int head[MAX_NODE];
Edge edges[MAX_EDGE];
void init() {
memset(head, 0, sizeof head);
edge_cnt = 0;
}
void add_edge(int from, int to, int weight) {
edges[++edge_cnt] = Edge(weight, to, head[from]);
head[from] = edge_cnt;
}
向前星,不懂的可以搜搜,一种类链式存储结构
接下来是dijkstra的代码
bool vis[MAX_NODE];
int dis[MAX_NODE];
int dijkstra(int s, int t) {
fors(i, MAX_NODE) {
vis[i] = false;
dis[i] = INT_MAX;
}
dis[s] = 0;
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > heap;
heap.push(make_pair(0, s));
while (true) {
int u = heap.top().second;
heap.pop();
if (u == t) return dis[t];
if (vis[u]) continue;
vis[u] = true;
for (int i = head[u]; i; i = edges[i].next) {
int v = edges[i].to;
if (dis[v] > dis[u] + edges[i].weight) {
dis[v] = dis[u] + edges[i].weight;
heap.push(make_pair(dis[v], v));
}
}
}
}
简单说明一下
s为源点,t为终点,u、v含义同上述
堆使用了c++自带的priority_queue,因为标准库默认是最大堆(太坑了T^T),所以要自己设置比较结构体
这里直接用了greater<pair<int,int>>(又是一个坑点,大于比较是最小堆,真是反人类。。。),因为pair<int,int>本身重载了 > 操作符,先比较 first 值再比较 second 值,这里就把dis放入first,点的标号放入second,堆就会自动对dis排序了
首先将源点设为边界点dis设为0,其余点为外部点。
将所有边界点加入堆中,每次取堆顶元素,注意,堆顶元素可能已经不是边界点而是内部点了,这是因为每个点的dis值可能会更新多次,每次都会入堆,最小的那个dis值最先出堆,出堆时更新其周围点的dis值,并将其加入到内部点中,之后出堆的就不用管了,直接continue.
这里只要找到终点的最短路径就提前退出了,如果想求所有点的最短路径的话,可以改成这样
bool vis[MAX_NODE];
int dis[MAX_NODE];
void dijkstra(int s) {
fors(i, MAX_NODE) {
vis[i] = false;
dis[i] = INT_MAX;
}
dis[s] = 0;
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > heap;
heap.push(make_pair(0, s));
while (!heap.empty()) {
int u = heap.top().second;
heap.pop(