![090644t797fce7n20of7j9.png](http://bbs.ahalei.com/data/attachment/forum/201403/31/090644t797fce7n20of7j9.png)
![090651l6pt4666tptut66u.png](http://bbs.ahalei.com/data/attachment/forum/201403/31/090651l6pt4666tptut66u.png)
![090657ofidcactthcig33i.png](http://bbs.ahalei.com/data/attachment/forum/201403/31/090657ofidcactthcig33i.png)
![090706vmjy7l2ee2lyalia.png](http://bbs.ahalei.com/data/attachment/forum/201403/31/090706vmjy7l2ee2lyalia.png)
![090714f2p1wppynngj2pep.png](http://bbs.ahalei.com/data/attachment/forum/201403/31/090714f2p1wppynngj2pep.png)
![090722ywunackk35i8cni5.png](http://bbs.ahalei.com/data/attachment/forum/201403/31/090722ywunackk35i8cni5.png)
![090730eq6oqzyq7laqha9y.png](http://bbs.ahalei.com/data/attachment/forum/201403/31/090730eq6oqzyq7laqha9y.png)
![090738azt5clcozl899ekt.png](http://bbs.ahalei.com/data/attachment/forum/201403/31/090738azt5clcozl899ekt.png)
将所有的顶点分为两部分:已知最短路程的顶点集合P和未知最短路径的顶点集合Q。最开始,已知最短路径的顶点集合P中只有源点一个顶点。我们这里用一个book[ i ]数组来记录哪些点在集合P中。例如对于某个顶点i,如果book[ i ]为1则表示这个顶点在集合P中,如果book[ i ]为0则表示这个顶点在集合Q中。
设置源点s到自己的最短路径为0即dis=0。若存在源点有能直接到达的顶点i,则把dis[ i ]设为e[s][ i ]。同时把所有其它(源点不能直接到达的)顶点的最短路径为设为∞。
在集合Q的所有顶点中选择一个离源点s最近的顶点u(即dis[u]最小)加入到集合P。并考察所有以点u为起点的边,对每一条边进行松弛操作。例如存在一条从u到v的边,那么可以通过将边u->v添加到尾部来拓展一条从s到v的路径,这条路径的长度是dis[u]+e[u][v]。如果这个值比目前已知的dis[v]的值要小,我们可以用新值来替代当前dis[v]中的值。
重复第3步,如果集合Q为空,算法结束。最终dis数组中的值就是源点到所有顶点的最短路径。
接下来是算法模板的部分。由于用到了“链式前向星”变相存储邻接矩阵,记忆有点模糊了的话戳讲解连接:https://blog.csdn.net/acdreamers/article/details/16902023
【dijkstra算法模板(省略主函数)】这里的注释有我自己的补充,对自己的理解很关键,看一看!
const int MAX_N = 10000; //边的max
const int MAX_M = 100000; //点的max
const int inf = 0x3f3f3f3f; //最大距离
struct edge { //v为边起点,u为边终点,w为边的权重
int v, w, next;
} e[MAX_M];
int p[MAX_N], eid , n; //p数组就是讲解中的head数组,eid就是讲解中的cnt计数用的。n是图的结点个数。
void mapinit() { //初始化图,和原来一样
memset(p, -1, sizeof(p)); //注意这里初始化-1,关系到dijkstra算法中的更新(松弛)if语句条件
eid = 0;
}
void insert(int u, int v, int w) { // 插入带权有向边,多了个权 (链式前向星存储)
e[eid].v = v;
e[eid].w = w;
e[eid].next = p[u];
p[u] = eid++;
}
void insert2(int u, int v, int w) { // 插入带权双向边
insert(u, v, w);
insert(v, u, w);
}
int dist[MAX_N]; // 存储单源最短路的结果
bool vst[MAX_N]; // 标记每个顶点是否在集合 U 中
bool dijkstra(int s) {
memset(vst, 0, sizeof(vst));
memset(dist, 0x3f, sizeof(dist));
dist[s] = 0;
for (int i = 0; i < n; ++i) {
int v, min_w = inf; // 记录 dist 最小的顶点编号和 dist 值
for (int j = 0; j < n; ++j) {
if (!vst[j] && dist[j] < min_w) {//没有访问并且满足要求
min_w = dist[j];
v = j;//更新
}
}
if (min_w == inf) { // 所有点都满足vst[v]==true了,都确定最短路长度了,而循环未结束,说明有顶点是源点无法到达的,anyway,算法该结束了
return false; //flase的意思是源点不能到达所有顶点
}
vst[v] = true; // 将顶点 v 加入集合 U 中,也就是说,源点到该顶点的最短路长度由估计值变为了确定值,存在dist[v]中。
for (int j = p[v]; j != -1; j = e[j].next) {
// 如果和 v 相邻的顶点 x 满足 dist[v] + w(v, x) < dist[x] 则更新 dist[x],这一般被称作“松弛”操作
int x = e[j].v;
if (!vst[x] && dist[v] + e[j].w < dist[x]) {//没有访问,满足提议
dist[x] = dist[v] + e[j].w;//更新
}
}
}
return true; // 源点可以到达所有顶点,算法正常结束
}
然而,其实你会发现这个算法复杂度挺高的,O(n^2),仔细一看,第一层循环肯定不能简化了,因为要求源点到每一个点的最短路长度;而第二层循环是可以简化的!因为其实遍历是为了求估计值中的最小值,把它确定下来,而如果用小根堆或者优先队列来存储dist的话,就可以直接取到最小值而无需遍历比较了!
【小根堆优化模板】
这里的小根堆是用set来伪实现的(谁去手写堆啊我c!),注意其中的元素是pair<int,int>类型的,注意用到less<pair<int,int> >来作为set的第二个参数让C++去自动根据pair的first数值从小到大排序!
然后还要注意对set进行插入时用到了make_pair:
- std::pair主要的作用是将两个数据组合成一个数据,两个数据可以是同一类型或者不同类型。
- 例如std::pair<int,float> 或者 std::pair<double,double>等。
- pair实质上是一个结构体,其主要的两个成员变量是first和second,这两个变量可以直接使用。
- 初始化一个pair可以使用构造函数,也可以使用std::make_pair函数,make_pair函数的定义如下:
- template pair make_pair(T1 a, T2 b) { return pair(a, b); }
然后还要注意用到了auto的“指针”去指向set.begin() (其实就是set<PII,less<PII> >::iterator)
其他就是迪杰斯特拉算法的常规步骤了。
const int MAX_N = 10000;
const int MAX_M = 100000;
const int inf = 0x3f3f3f3f;
struct edge {
int v, w, next;
} e[MAX_M];
int p[MAX_N], eid, n;
void mapinit() {
memset(p, -1, sizeof(p));
eid = 0;
}
void insert(int u, int v, int w) { // 插入带权有向边
e[eid].v = v;
e[eid].w = w;
e[eid].next = p[u];
p[u] = eid++;
}
void insert2(int u, int v, int w) { // 插入带权双向边
insert(u, v, w);
insert(v, u, w);
}
typedef pair<int, int> PII;
set<PII, less<PII> > min_heap;
/*用 set 来伪实现一个小根堆,并具有映射二叉堆的功能。堆中 pair<int, int> 的 second 表示顶点下标,first 表示该顶点的 dist 值,注意,只要写上less<PII>就会一first为标准排序,C++,内部实现,不需要管*/
int dist[MAX_N]; // 存储单源最短路的结果
bool vst[MAX_N]; // 标记每个顶点是否在集合 U 中
bool dijkstra(int s) {
// 初始化 dist、小根堆和集合 U
memset(vst, 0, sizeof(vst));
memset(dist, 0x3f, sizeof(dist));
min_heap.insert(make_pair(0, s));
dist[s] = 0;
for (int i = 0; i < n; ++i) {
if (min_heap.size() == 0) { // 如果小根堆中没有可用顶点,说明有顶点无法从源点到达,算法结束
return false;
}
// 获取堆顶元素,并将堆顶元素从堆中删除
auto iter = min_heap.begin(); //auto的“指针!”数据类型都用上了,为C++11打call!
int v = iter->second;
min_heap.erase(*iter);
vst[v] = true;
// 进行和普通 dijkstra 算法类似的松弛操作
for (int j = p[v]; j != -1; j = e[j].next) {
int x = e[j].v;
if (!vst[x] && dist[v] + e[j].w < dist[x]) { //对未确定的估计值进行更新。
// 先将对应的 pair 从堆中删除,再将更新后的 pair 插入堆
min_heap.erase(make_pair(dist[x], x));
dist[x] = dist[v] + e[j].w;
min_heap.insert(make_pair(dist[x], x));
}
}
}
return true; // 存储单源最短路的结果
}
优先队列优化模板的代码可见:https://blog.csdn.net/ergedathouder/article/details/52439438 (会一个就行了,原理都一样,最多代码填空可能考到这个)