赞
踩
Dijkstra是处理单源最短路的算法,要求边权为正。
假设起点是st;已经确定到st最短距离的点在集合S中
for(i = 0; i < n; i ++ )
if(dis[y] > dis[x] + w(x, j)) dis[j] = dis[x] + w(x, y)
第二步循环内每次都能确定一个点x的最短距离,重复n次就能确定所有点的最短距离。
以算法导论的图为例:
a. 初始化dis[s]=0
b. 到s最近的就是s本身,更新s连的y和t,
S
=
{
s
}
S=\{s\}
S={s}
c. 到s最近的是y(
y
:
5
<
t
:
10
<
x
:
∞
=
z
:
∞
y:5 < t:10<x:\infty=z:\infty
y:5<t:10<x:∞=z:∞),更新y连的t、x、z,
S
=
{
s
,
y
}
S=\{s,y\}
S={s,y}
d. 到s最近的是z(
z
:
7
<
t
:
8
<
x
:
14
z:7 < t:8 < x:14
z:7<t:8<x:14),更新z连的x,
S
=
{
s
,
y
,
z
}
S=\{s,y,z\}
S={s,y,z}
e. 到s最近的是t(
t
:
8
<
x
:
13
t:8 < x:13
t:8<x:13),更新t连的x,
S
=
{
s
,
y
,
z
,
t
}
S=\{s,y,z,t\}
S={s,y,z,t}
f. 到s最近的是x(只剩x了),结束。
假设u之前的点,有性质:对于所有加入S的点,在这个点加入S时,根据算法的步骤所确定的st到这个点的距离,都是最短路。
记:根据算法的步骤所确定的 s t st st到点 i i i的距离 = d i s [ i ] dis[i] dis[i]; s t st st到点 i i i的实际最短路= a n s [ i ] ans[i] ans[i]
那么性质可简写为:对于所有加入S的点 i i i,在 i i i加入S时,都有 d i s [ i ] = a n s [ i ] dis[i]=ans[i] dis[i]=ans[i]。
命题如下:点
u
u
u加入S时,
u
u
u是第一个使这个性质不成立的点,即
d
i
s
[
u
]
≠
a
n
s
[
u
]
dis[u] \neq ans[u]
dis[u]=ans[u]
利用反证法证明不存在这样的点 u u u:
题目链接
这题70%的数据1≤n≤1000,n较小可采用邻接矩阵,对于n较大可采用邻接链表。
遇到重边时,采用邻接矩阵需要取最小的边,采用邻接链表则不需要考虑。
// 邻接矩阵 #include <iostream> #include <cstring> using namespace std; const int N = 1005; int n, m, dis[N], st; int g[N][N]; bool flag[N]; // 该点最短路是否已经被确定(进入集合S) void dijkstra() { memset(dis, 0x3f, sizeof dis); memset(flag, 0, sizeof flag); dis[st] = 0; for (int i = 0; i < n; i ++ ) { int x = -1; // 没确定最短路中离起点最近的点 for (int j = 1; j <= n; j ++ ) { if (!flag[j] && (x == -1 || dis[x] > dis[j])) x = j; } flag[x] = true; for (int j = 1; j <= n; j ++ ) { dis[j] = min(dis[j], dis[x] + g[x][j]); } } } int main() { scanf("%d%d%d", &n, &m, &st); memset(g, 0x3f, sizeof g); while(m -- ) { int u, v, w; scanf("%d%d%d", &u, &v, &w); g[u][v] = min(g[u][v], w); // 重边取最小的边 } dijkstra(); for (int i = 1; i <= n; i ++ ) { if (dis[i] == 0x3f3f3f3f) printf("2147483647 "); else printf("%d ", dis[i]); } return 0; }
当n较大时,如1≤n≤100000,那么
O
(
n
2
)
O(n^2)
O(n2)的算法将会超时,需要用邻接链表+堆优化。
回顾Dijkstra的流程:
for(i = 0; i < n; i ++ )
if(dis[y] > dis[x] + w(x, j)) dis[j] = dis[x] + w(x, y)
其中2.1可以采用小根堆维护
d
i
s
[
i
]
dis[i]
dis[i],直接挑出距离
s
t
st
st最近的点,而不需要遍历所有点。
这样2.1的时间就变成
O
(
1
)
O(1)
O(1),但是堆的修改操作时间复杂度是
O
(
l
o
g
n
)
O(logn)
O(logn),整个算法至多需要修改
m
m
m条边(松弛会用到每个点的所有边,所有点都要进行松弛操作)。
直到堆为空停止,而不是采用for循环。
因此时间复杂度是
O
(
m
l
o
g
n
)
O(mlogn)
O(mlogn)
注意如果用STL的优先队列,由于无法修改元素,只能往里面插新的元素,因此时间复杂度可能变成 O ( m l o g m ) O(mlogm) O(mlogm),但是 m ≤ n 2 m\leq n^2 m≤n2, l o g m ≤ 2 l o g n logm\leq 2logn logm≤2logn,因此 O ( m l o g m ) O(mlogm) O(mlogm)等价于 O ( m l o g n ) O(mlogn) O(mlogn)
#include <iostream> #include <cstring> #include <queue> using namespace std; typedef pair<int, int> PII; const int N = 100005; const int M = 500005; int n, m, dis[N], st; int head[N], nxt[M], lnk[M], val[M], idx; bool flag[N]; // 该点最短路是否已经被确定(进入集合S) void add(int u, int v, int w) { lnk[idx] = v; val[idx] = w; nxt[idx] = head[u]; head[u] = idx ++ ; } void dijkstra() { memset(dis, 0x3f, sizeof dis); memset(flag, 0, sizeof flag); priority_queue<PII, vector<PII>, greater<PII> > heap; // 小根堆,存{dis[x], 结点x} heap.push({0, st}); // 堆默认用pair的first排序 dis[st] = 0; while (heap.size()) { auto t = heap.top(); heap.pop(); int x = t.second; if (flag[x]) continue; flag[x] = true; for (int i = head[x]; i != -1; i = nxt[i]) { int y = lnk[i]; int w = val[i]; if (dis[y] > dis[x] + w) { dis[y] = dis[x] + w; heap.push({dis[y], y}); } } } } int main() { scanf("%d%d%d", &n, &m, &st); memset(head, -1, sizeof head); while(m -- ) { int u, v, w; scanf("%d%d%d", &u, &v, &w); add(u, v, w); } dijkstra(); for (int i = 1; i <= n; i ++ ) { if (dis[i] == 0x3f3f3f3f) printf("2147483647 "); else printf("%d ", dis[i]); } return 0; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。