您的位置:首页 > 其它

最短路小结 Floyd + Dijkstra + 带花费 + 字符节点

2017-07-21 15:42 197 查看

Floyd



ps:有向图是带有方向的图,就是有箭头的图,此介绍以有向图为例,无向图不计方向。

初始工作把这张有向图用一个二维数组存储,第一个下表表示起点,第二个表示终点,数组值代表长度。

如下图:

1234
10264
203
3701
45120
自己到自己的距离为 0

从表中看的出,部分路径并不是最短,而且有些路径没有表示出来,所以我们需要更新这个二维数组。

那么如果两点之间不是最短距离,那么需要一个点来中专减少距离

比如:由 4 到 3,直接的话距离为 12,但是经过 节点1 的中转, 变为了 11 ,如果经过 1 和 2 的中转,变为了 10。

假设现在只允许通过 节点1 中转,假设现在从 ij 点,那么长度为G[i][j], 如果经过 节点1 的话,那么长度就变为 G[i][1] + G[1][j]

如果

for(int i = 1; i <= n ; i++)
for(int j = 1; j <= n; j++)
if(G[i][j] > G[i][1] + G[1][j])
G[i][j] = G[i][1] + G[1][j];


以此类推,把每个节点遍历一遍。

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];


1234
10254
29034
36801
457100
这时候可能会问,为什么从 4 到 3 为什么是 10,不是经过一个节点吗,应该是 11,但是在 1 到 3 的时候也用了这个方法,他选择了有节点的走法,然后到 4 和 3 的时候就判断走 不走1节点,1节点以后的长度已经最优。

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2544

参考代码

#include<iostream>
#define inf 0x3f3f3f3f
using namespace std;
int G[10010][10010];
int main () {
int n, m;
while(~scanf("%d %d", &n, &m) && n) {
for(int i = 0; i <= n; i++) {
for(int j = 0; j <= n; j++) {
if(i == j) G[i][j] = G[j][i] = 0;
else G[i][j] = G[j][i] = inf;
}
}
int x, y, w;
for(int i = 1; i <= m; i++) {
cin >> x >> y >> w;
if(G[x][y] > w) {
G[x][y] = G[y][x] = w;
}
}
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];
}
}
}
cout << G[1]
<< endl;
}
return 0;
}


下面介绍 Dijkstra



首先还是需要一个二维数组来记录

123456
10112
209
305
4401314
504
60
这次不在G数组中更新数值,而是创建一个 dis 数组,来记录长度。

123456
dis0112
我们设起点为 1 , 那么dis[1] = 0;(设谁为起点谁为0)

因为是求 1 到其他个点的长度,那么首先就是想找离 1 最近的点,那就是 2 点, 选择了2号顶点, 而且没有中转比它还小,因为 2 号点是里面最小的点了,没有谁能比它还小,而且还需要中转。

那么找到2号顶点之后,引申出两个点34

1 → 2 → 3, 和 1→3, 比较哪个更近, 写入dis[3],(起点默认为1)

换成代码就应该是

if(dis[3] > dis[2] + G[2][3])
dis[3] = dis[2] + G[2][3];


dis[3] = 12

dis[2] + G[2][3] = 1 + 9 = 10

所以dis[3] = 10

同理 2→4 的时候也是比较 dis[4]dis[2] + G[2][4]

最后如图:

123456
dis01841317

小总结

先说读取,把G数组也就是map,全部记为inf 无限大,然后读取数值,更改两点之间的长度。

dis数组初始为直接连点的长度,即图上显示直接连接的长度,如果没有就记为inf

vis数组初始先标记起点

更新dis数组:

1.找到离起点,即与0最近的点,

2.vis标记这个点

3.用这个点为中转站,判断到终点哪个更短

4.更新到dis数组中

这样dis存的点为起点到各个点的最短距离了

练习题目:和上面一样

ac代码:

#include<iostream>
#include<cstring>
#define inf 0x3f3f3f3f
using namespace std;
int n, m;
int vis[10010];
int dis[10010];
int G[10010][10010];
void dijkstra(int n) {
for(int i = 1; i <= n; i++) {
dis[i] = G[1][i];
}
memset(vis, 0, sizeof(vis));
vis[1] = 1;
for(int cnt = 1; cnt <= n-1; cnt++) {
int temp = inf;
int u;
for(int i = 1; i <= n; i++) {
if(!vis[i] && dis[i] < temp) {
temp = dis[i];
u = i;
}
}
if(temp == inf) break;
vis[u] = 1;
for(int i = 1; i <= n; i++) {
if(dis[i] > dis[u] + G[u][i])
dis[i] = dis[u] + G[u][i];
}
}
}
int main () {
while(~scanf("%d %d", &n, &m) && n+m) {
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
if(i == j) G[i][j] = G[j][i] = 0;
else G[i][j] = G[j][i] = inf;
}
}
int a, b, c;
for(int i = 1; i <= m; i++) {
cin >> a >> b >> c;
G[a][b] = G [b][a] = c;
}
dijkstra(n);
printf("%d\n", dis
);
}
return 0;
}


Dijkstra带有花费的最短最省

顾名思义就是每条路不仅有长度,而且有花费,优先选择路程最少的,如果一样的话,优先选择花费少的。

这里我们选择使用结构体来做

struct {
int w, v;
}s[1010][1010], dis[1010];


s代表地图, 即上题中的G数组,dis还是dis, w代表路程,v代表花费,初始的时候要初始化

void init(int n) {
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
s[i][j].w = s[i][j].v = inf;
}
}
for(int i = 1; i <= n; i++)
dis[i].v = dis[i].w = inf;
}


初始化全部为inf ,即无限大

然后数据输入,这里有个判定,

当输入的起点终点一样

花费后来输入的小,那么就要修改花费值

路程后来输入的小,那么就要修改路程

for(int i = 1; i <= m; i++) {
scanf("%d %d %d %d", &x, &y, &w, &v);
if(s[x][y].w > w || (s[x][y].w == w && s[y][x].v > v)) {
s[x][y].w = s[y][x].w = w;
s[x][y].v = s[y][x].v = v;
}
}


在dijkstra函数内,更新dis数组的时候 除了dis[i].w > dis[u].w + s[u][i].w之外,还要修改当dis[i].w == dis[u].w + s[u][i].w 时的花费值,dis[i].w == dis[u].w + s[u][i].w && dis[i].v > dis[u].v + s[u][i].v

保证选择当路程一样的时候花费也是最小的

for(int i = 1; i <= n; i++) {
if(dis[i].w > dis[u].w + s[u][i].w || (dis[i].w == dis[u].w + s[u][i].w && dis[i].v > dis[u].v + s[u][i].v)) {
dis[i].w = dis[u].w + s[u][i].w;
dis[i].v = dis[u].v + s[u][i].v;
}
}


题目链接 :http://acm.hdu.edu.cn/showproblem.php?pid=3790

AC代码:

#include<iostream>
#include<cstring>
#define inf 0x3f3f3f3f
using namespace std;
int vis[10010];
struct { int w, v; }s[1010][1010], dis[1010];
void init(int n) { for(int i = 1; i <= n; i++) { for(int j = 1; j <= n; j++) { s[i][j].w = s[i][j].v = inf; } } for(int i = 1; i <= n; i++) dis[i].v = dis[i].w = inf; }
void dijkstra(int st, int ed, int n) {
dis[st].w = 0;
dis[st].v = 0;
memset(vis, 0, sizeof(vis));
for(int cnt = 1; cnt <= n; cnt++) {
int minn = inf;
int u = - 1;
for(int i = 1; i <= n; i++) {
if(vis[i] == 0 && dis[i].w < minn) {
minn = dis[i].w;
u = i;
}
}
if(u == -1) break;
vis[u] = 1;
for(int i = 1; i <= n; i++) { if(dis[i].w > dis[u].w + s[u][i].w || (dis[i].w == dis[u].w + s[u][i].w && dis[i].v > dis[u].v + s[u][i].v)) { dis[i].w = dis[u].w + s[u][i].w; dis[i].v = dis[u].v + s[u][i].v; } }
}
}
int main () {
int n, m;
while(~scanf("%d %d", &n, &m) && n+m) {
init(n);
int x, y, w, v;
for(int i = 1; i <= m; i++) { scanf("%d %d %d %d", &x, &y, &w, &v); if(s[x][y].w > w || (s[x][y].w == w && s[y][x].v > v)) { s[x][y].w = s[y][x].w = w; s[x][y].v = s[y][x].v = v; } }
int st, ed;
scanf("%d %d", &st, &ed);
dijkstra(st, ed, n);
printf("%d %d\n", dis[ed].w, dis[ed].v);
}
return 0;
}


名字为字母的最短路

当输入并不是1234点的时候,我们需要把它归结于数字,然后用通常的方法去解;

这里使用的是STL中的map

定义为

map<string, int> cnt;


利用map的功能,每次可以从map中查询有没有出现过这个地名,如果没有,那么附一个数值kase给这个地名,然后kase++,保证每次赋的值是不同的,cnt.count(s1) 的意思是在cnt中查询有没有出现过s1字符串,如果没有返回非,如果有返回是,一旦把这个工作做完,cnt[s1]就代表一个数字,就如前面题目中的1234,把地名转换为数字来求解

for(int i = 0; i < n; i++) {
cin >> s1 >> s2 >> x;
if(!cnt.count(s1)) cnt[s1] = ++t;
if(!cnt.count(s2)) cnt[s2] = ++t;
G[cnt[s2]][cnt[s1]] = G[cnt[s1]][cnt[s2]] = min(x, G[cnt[s1]][cnt[s2]]);
}


练习题目 :http://acm.hdu.edu.cn/showproblem.php?pid=3790

AC代码 :

#include<map>
#include<iostream>
#include<string>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int inf = 0x3f3f3f3f;
int G[155][155], vis[155], dis[155];
map<string, int> cnt;
int t, n;

int dijkstra(int S,int T) {
int u;
memset(vis,0,sizeof(vis));
for(int i = 0; i <= t; i++) dis[i] = G[S][i];
dis[S] = 0;
vis[S] = 1;
for(int i = 1; i < n; i++) {
int temp = inf;
for(int j = 0; j <= t; j++) {
if(!vis[j] && dis[j] < temp) {
temp = dis[j];
u = j;
}
}
if(temp == inf) continue;
vis[u] = true;
for(int j = 0; j <= t; j++) {
if(!vis[j] && dis[j] > dis[u] + G[u][j])
dis[j] = dis[u] + G[u][j];
}
}
return dis[T];
}

int main() {
int S, T, x;
string s1, s2;
while(scanf("%d", &n) && n != -1) {
t = -1;
cnt.clear();
memset(G, inf, sizeof(G));
cin >> s1 >> s2;
cnt[s1] = ++t;
S = t;
if(!cnt.count(s2))
cnt[s2] = ++t;
T = t;
for(int i = 0; i < n; i++) { cin >> s1 >> s2 >> x; if(!cnt.count(s1)) cnt[s1] = ++t; if(!cnt.count(s2)) cnt[s2] = ++t; G[cnt[s2]][cnt[s1]] = G[cnt[s1]][cnt[s2]] = min(x, G[cnt[s1]][cnt[s2]]); }
int ans = dijkstra(S, T);
if(ans == inf) printf("-1\n");
else printf("%d\n", ans);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: