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

来源:互联网 发布:印度软件外包 编辑:程序博客网 时间:2024/05/22 05:19

Floyd

这里写图片描述

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

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

1 2 3 4 1 0 2 6 4 2 ∞ 0 3 ∞ 3 7 ∞ 0 1 4 5 ∞ 12 0

自己到自己的距离为 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];
1 2 3 4 1 0 2 5 4 2 9 0 3 4 3 6 8 0 1 4 5 7 10 0

这时候可能会问,为什么从 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][n] << endl;    }    return 0;} 

下面介绍 Dijkstra

这里写图片描述

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

1 2 3 4 5 6 1 0 1 12 ∞ ∞ ∞ 2 ∞ 0 9 ∞ ∞ ∞ 3 ∞ ∞ 0 ∞ 5 ∞ 4 ∞ ∞ 4 0 13 14 5 ∞ ∞ ∞ ∞ 0 4 6 ∞ ∞ ∞ ∞ ∞ 0

这次不在G数组中更新数值,而是创建一个 dis 数组,来记录长度。

1 2 3 4 5 6 dis 0 1 12 ∞ ∞ ∞

我们设起点为 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]
最后如图:

1 2 3 4 5 6 dis 0 1 8 4 13 17

小总结

先说读取,把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[n]);    }    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;}
原创粉丝点击