问题简介
最短路径问题: 从图中的某个顶点出发到达另外一个顶点的所经过的边的权重和最小的一条路径,称为最短路径。 对于无权图,可以假设每条边的权重是1。 这里分别介绍一下Dijkstra算法和Floyd算法。
Dijkstra算法
迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,采用广度优先遍历,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。
示例
我们需要求得上图中
V
1
V_1
V1?到各个顶点的最短距离(
V
=
{
V
1
,
V
2
,
V
3
,
V
4
,
V
5
,
V
6
}
V=\{V_1, V_2, V_3, V_4, V_5, V_6\}
V={V1?,V2?,V3?,V4?,V5?,V6?})。首先我们初始化两个集合,
T
T
T和
S
S
S。
T
T
T为已经求得距离的顶点的集合,
S
S
S为剩余顶点。初始的时候,
T
=
{
V
1
}
T=\{V_1\}
T={V1?},
S
=
{
V
?
T
}
S=\{V-T\}
S={V?T}。 再初始化一个
d
i
s
t
dist
dist数组,代表目前已经求得的最短距离:
然后。我们要找到与集合
V
1
V_1
V1?中元素距离最近的顶点,便是
V
3
V_3
V3?,距离为10。那么此时的“10”便是确定值了(因为目前离
V
1
V_1
V1?顶点最近的是
V
3
V_3
V3?顶点,并且这个图所有的边都是正数,那么肯定不可能通过第三个顶点中转,使得
V
1
V_1
V1?顶点到
V
3
V_3
V3?顶点的路程进一步缩短了。假如
V
1
V_1
V1?通过
V
x
V_x
Vx?再到
V
3
V_3
V3?距离更短,这与
V
1
V_1
V1?到各顶点距离最短的是
V
3
V_3
V3?矛盾,所以并不存在
V
x
V_x
Vx?),我们将
V
3
V_3
V3?加入到集合
T
T
T中。 此时的
T
T
T被更新过了,那么代表
d
i
s
t
dist
dist数组中的元素也要进行更新,可以发现顶点
V
3
V_3
V3?有一条弧为
<
V
3
,
V
4
>
<V_3, V_4>
<V3?,V4?>,且
V
1
?
>
V
3
?
>
V
4
V_1->V_3->V_4
V1??>V3??>V4?的距离为60,比原来的
V
1
?
>
V
4
V_1->V_4
V1??>V4?的距离(
∞
\infty
∞代表无法到达)更小,所以将
d
i
s
t
dist
dist数组进行更新:
蓝色的表明已经是确定值,不会再更改,红色表明该距离刚才才更新过。此时,我们重复上述操作,在未标蓝的距离中(
∞
,
60
,
30
,
100
\infty, 60, 30, 100
∞,60,30,100)找到最小的距离,为
V
5
V_5
V5?,距离是“30”。那么,将
V
5
V_5
V5?加入
T
T
T中,通过
V
5
V_5
V5?的边再次更新
d
i
s
t
dist
dist:
然后继续循环:
这便是顶点
V
1
V_1
V1?到各个顶点的最短距离,其中,到
V
2
V_2
V2?的距离为
∞
\infty
∞代表着顶点
V
1
V_1
V1?无法到达
V
2
V_2
V2?。
代码
#include <iostream>
#include <cstring>
using namespace std;
#define INF 0x3f3f3f3f
const int maxn = 1e3 + 7;
int dist[maxn];
int edge[maxn][maxn];
bool visited[maxn];
int v, n, m;
void init(){
memset(dist, INF, sizeof dist);
memset(edge, INF, sizeof edge);
memset(visited, false, sizeof visited);
}
void dijkstra(){
for(int i = 0; i <= n; i++){
dist[i] = edge[v][i];
}
dist[v] = 0;
visited[v] = true;
for(int i = 2; i <= n; i++){
int temp = INF;
int u = v;
for(int j = 1; j <= n; j++){
if(!visited[j] && dist[j] < temp){
u = j;
temp = dist[j];
}
}
visited[u] = true;
for(int j = 1; j <= n; j++){
if (!visited[j] && edge[u][j] < INF){
int newdist = dist[u] + edge[u][j];
if (newdist < dist[j]){
dist[j] = newdist;
}
}
}
}
}
int main(){
while(scanf("%d%d", &n, &m) && n && m){
init();
int x, y, t;
for(int i = 1; i <= m; i ++){
cin>>x>>y>>t;
edge[x][y] = t;
}
v = 1;
dijkstra();
for(int k = 1; k <= n; k++){
if (dist[k] == INF){
cout<<"INF"<<' ';
}else{
cout<<dist[k]<<' ';
}
}
}
}
Floyd算法
Floyd算法相对来说更容易理解一点。 首先初始化一个二维数组,保存目前顶点两两之间的距离,无法到达的顶点设为
∞
\infty
∞。 这里先贴代码:
#include <iostream>
#include <cstring>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 1e3 + 7;
int G[maxn][maxn];
int path[maxn][maxn];
int n, m;
void init(){
memset(G, INF, sizeof G);
memset(path, -1, sizeof path);
for ( int i = 0; i < maxn; i ++){
G[i][i] = 0;
}
}
void floyd(){
for (int k = 1; k <= n; k ++){
for (int i = 1; i <= n; i++){
for (int j = 1; j<= n; j++){
if (G[i][j] > G[i][k] + G[k][j]){
G[i][j] = G[i][k] + G[k][j];
path[i][j] = k;
}
}
}
}
}
void print_path(int u, int v){
cout<<u<<"->";
while (path[u][v] != -1){
u = path[u][v];
cout<<u<<"->";
}
cout<<v<<endl;
}
int main(){
while (scanf("%d%d", &n, &m) && n && m){
init();
int x, y, t;
for (int i = 0; i < m; i++){
scanf("%d%d%d", &x, &y, &t);
G[x][y] = t;
}
floyd();
for (int q = 1; q <= n; q++){
if (G[1][q] == INF){
cout<<"INF"<<' '<<endl;
cout<<"无法到达"<<endl;
}else{
cout<<G[1][q]<<' '<<endl;
print_path(1, q);
}
}
}
}
搞懂Floyd算法的关键就是代码中的k的循环,i和j就是两个顶点,k代表i到j允许经过顶点k,然后计算通过顶点k时的距离是否更短,是的话便更新。然后k再循环到下一个顶点,一直到k循环完毕,便意味着i到j,允许经过所有顶点时,最短的距离。
Dijkstra和Floyd的区别
总结:
- Dijkstra不能处理负权图,Flyod能处理负权图;
- Dijkstra处理单源最短路径,Flyod是处理多源最短路径;
- Dijkstra时间复杂度为
O
(
n
2
)
O(n^2)
O(n2),Floyd时间复杂度为
O
(
n
3
)
O(n^3)
O(n3);
所以:所以题目中如果是单源点正权图,就用Dijkstra;如果是任意两个点之间的最短路径或者是负权图,就用Floyd。
|