王道论坛机考系列——图论之最短距离

图论

  • 王道论坛机考系列——图论之最短距离
    • 最短距离——Floyd算法(解决全源)
      • 最短路——Floyd算法
      • 最短路——Dijkstra算法一
      • 最短路径问题——Dijkstra算法二

王道论坛机考系列——图论之最短距离

最短距离——Floyd算法(解决全源)

寻找图中某两个特定节点之间的最短路径的长度。
图使用邻接矩阵edge[i][j]表示,edge[i][j]最初始的时候表示的是从节点i不经过任何节点到节点j的距离,如果不存在则设置为无穷大。那么从节点i开始可以经过节点l再到节点j,此时需要比较edge[i][l]+edge[l]][j]与edge[i][j]的大小,如果前者比较小,那么edge[i][j]的值需要更换一下(其中edge[i][l]表示从i到l不经过任何节点的距离,edge[l][j]表示从l到j不经过任何中间节点的距离),换做更一般的情况,当从i到j的中间节点数量增多时,可以使用ans[k][i][j]表示从节点i开始只经过小于等于k的节点到达j节点时的最短路径。计算的过程只需要从k等于1到n计算图中所有节点i到节点j之间的距离, ans[0][i][j]表示从i到j的直接距离。

使`cpp描述短发可以表示如下所示:

for (int k = 1; k <= n; k++) {
    //  计算只经过小于等于k的节点到达j节点时的最短路径
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            //  遍历所有的i,j
            if (ans[k-1][i][k] == INT_MAX || ans[k-1][k][j] == INT_MAX) {
                //  表示经过前k-1个节点时,i或者j不与k相通
                ans[k][i][j] = ans[k-1][i][j];
                // 保持原值,即从i到j经过k个节点和经过k-1个节点最终得到的结果是一样的
                continue;  //继续执行程序
            }

            if (ans[k-1][i][j] == INT_MAX ||
                ans[k-1][i][k] + ans[k-1][k][j] < ans[k-1][i][j])
                ans[k][i][j] = ans[k-1][i][k] + ans[k-1][k][j];
            else
                ans[k][i][j] = ans[k-1][i][j];
        }
    }
}

上面的方法中使用ans[k-1][i][j]的值来推导得到ans[k][i][j]的值时,值得注意的是ans[k][i][k] 和 ans[k][k][j]的值与 ans[k-1][i][k]和 ans[k-1][k][j]的值相同。所以程序更新为以下的内容,程序的运行结果与上面的运行结果是一样的,成功的将需要使用三位数组来解决的问题变成了只需要使用二维数组就可以解决的问题:

for (int k = 1; k <= n; k++) {
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if(ans[i][k] == INT_MAX || ans[k][j] == INT_MAX)
                continue;
            if (ans[i][j] == INT_MAX || ans[i][k] + ans[k][j] < ans[i][j])
                    ans[i][j] = ans[i][k] + ans[k][j];
        }
    }
}

最短路——Floyd算法

输入:包含多组数据,每组数据的第一行是两个整数N和M(N <= 100, M <= 10000),N表示节点的个数,M则表示图中存在M条边,接下来输入有M行,每一行包含三个数A, B, C(1 <= A,B <= 100, C <= 1000)A, B表示节点的编号,C表示节点A到节点B的长度,当输入N与M均为0时,表示结束。
输出:对于每一组输入,输出一行,表示从节点1到节点N的最短路径。

样例输入:
2 1
1 2 3
3 3
1 2 5
2 3 5
3 1 2
0 0
#include 
#include 
#define NUM 1001
using namespace std;

int arr[NUM][NUM];  //  用来表示图的矩阵

void FloydMinPath(int ans[][NUM], int n) {
    for (int k = 1; k <= n; k++) {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <+ n; j++) {
                if (ans[i][k] == INT_MAX || ans[k][j] == INT_MAX)
                    continue;
                if (arr[i][j] == INT_MAX || arr[i][k] + arr[k][j] < arr[i][j])
                    arr[i][j] = arr[i][k] + arr[k][j];
            }
        }
    }
}

int main() {
    int n, m;
    while(scanf("%d%d", &n, &m) != EOF) {
        if(n > 0 && m > 0) {
            for (int i = 0; i < NUM; i++)
                for (int j = 0; j < NUM; j++)
                arr[i][j] = INT_MAX;  //  初始化矩阵
            int a, b, c;
            for (int i = 0; i < m; i++) {
                scanf("%d%d%d", &a, &b, &c);
                arr[a][b] = c;
                arr[b][a] = c;   //  这里需要特别注意,我第一次的时候就忘记了
            }
            FloydMinPath(arr, n);
            printf("%d\n", arr[1][n]);
        } else {
            break;
        }
    }
    return 0;
}

弗洛伊德算法的时间复杂度是O(n3),空间复杂度是O(n2),并且弗洛伊德算法不适合于节点之间的距离为负数的情况。

最短路——Dijkstra算法一

本道题目使用Dijkstra算法计算从节点1到其余所有节点的最短路径。

#include 
#include 
#include 
#define NUM 101
using namespace std;

struct Edge {  //  邻接链表中的链表元素结构体
    int next;  //  一条边上的另一个顶点
    int len;   //  边的长度
};

vector<Edge> edge[NUM];

bool mark[NUM];  //  标记节点是否被加入集合中
int dis[NUM];    //  记录从节点1到其余所有节点的距离

void Dijkstra(int n, int root) {
    for (int i = 0; i <= n; i++) {
        //  初始化节点和距离
        mark[i] = false;
        dis[i] = -1;
    }
    mark[root] = true;  //  将源节点加入到K集合中;
    dis[root] = 0;      //  节点自己到自己的距离为0;
    int newP = root;
    for (int k = 1; k < n; k++) {
        //  进行n-1此循环,从节点root开始,按照最短路径递增的顺序
        //  每次添加一个新的节点;

        for (int j = 0; j < edge[newP].size(); j++) {
            //  查找顶点newP的所有相邻的边
            int next = edge[newP][j].next;
            int len = edge[newP][j].len;
            if (mark[next]) continue;  //  如果已经在K集合里面就不用管了
            if (dis[next] == -1 || dis[next] > len + dis[newP])
                dis[next] = dis[newP] + len;
        }

        // 查找最小的边;
        int min = INT_MAX;
        for (int i = 1; i <= n; i++) {
            if (mark[i]) continue;
            if (dis[i] == -1) continue;  // 表示不可达
            if (dis[i] < min) {
                min = dis[i];
                newP = i;
            }
        }

        //  最短路径的另一个顶点作为下一次的起点
        mark[newP] = true;
    }
}

int main() {
    int n, m;
    while(scanf("%d%d", &n, &m) != EOF) {
        if (n == 0 && m == 0) break;
        int a, b, c;
        while(m--) {
            scanf("%d%d%d", &a, &b, &c);
            Edge e;
            e.next = b;
            e.len = c;
            edge[a].push_back(e);
            e.next = a;
            edge[b].push_back(e);
        }
        Dijkstra(n, 1);
        printf("%d\n", dis[n]);
    }
    return 0;
}

以上的实现思路是借助王道机试指南上的做法的,第一次自己实现失败是因为没有正确理解Dijkstra算法,再加上对于每一个顶点的边的处理不是很恰当,以上的做法使用结构体数组来保存,对于每一个节点保存下它的相邻的边,在查找节点的边的时候就会很便捷,这种思路对于个人来说还是比较新奇的。

最短路径问题——Dijkstra算法二

题目描述:

给你n个点m条无向边,每条边都有长度d和花费p,给你起点s和终点t,要求输出起点到终点的最短距离及其花费,如果最短距离有多条路线,则输出花费最少的。

输入:输入n,m,点的编号是1~n,然后是m行,每行四个数a, b,d, p,表示a,b之间有一条边,且其长度为d,花费为p。最后一行是两个数s,t。起点s,终点t。n和m为0时输入结束。(1 < n <= 1000, 0 < m < 100000, s != t)。

输出:输出一行两个数,最短距离及其花费。

样例输入:
3 2
1 2 5 6
2 3 4 5
1 3
0 0
样例输出:
9 11
#include 
#include 
#include 
#define NUM 101
using namespace std;

struct Edge {  //  邻接链表中的链表元素结构体
    int next;  //  一条边上的另一个顶点
    int len;   //  边的长度
    int cost;  //  代价
};

vector<Edge> edge[NUM];

bool mark[NUM];  //  标记节点是否被加入集合中
int dis[NUM];    //  记录从开始节点到其余所有节点的距离
int cost[NUM];   //  记录从开始节点到其余所有节点的代价

void Dijkstra(int n, int start) {
    for (int i = 0; i <= n; i++) {
        mark[i] = false;
        dis[i] = -1;
        cost[i] = -1;
    }

    int newP = start;
    mark[newP] = true;
    dis[newP] = 0;
    cost[newP] = 0;

    for (int k = 1; k < n; k++) {
        //  从起始节点开始,以最短距离递增的规律每次增加一个节点
        for (int i = 0; i < edge[newP].size(); i++) {
            //  查找节点的相邻边
            int ne = edge[newP][i].next;
            int le = edge[newP][i].len;
            int co = edge[newP][i].cost;

            if (mark[ne]) continue;
            //  if (dis[ne] == -1 || dis[ne] > dis[newP] +le) {
            //  上面的内容是原版的内容
            if (dis[ne] == -1 || dis[ne] > dis[newP] + le ||
                (dis[ne] == dis[newP] + le && cost[ne] > cost[newP] + co)) {
                //  更新的时候将距离相同单花费更少的也作为更新的条件之一
                dis[ne] = dis[newP] + le;
                cost[ne] = cost[newP] + co;
            }
        }

        //  寻找最小的节点
        int min = INT_MAX;
        int cost_ = INT_MIN;
        for (int i = 1; i <= n; i++) {
            if (mark[i]) continue;
            if (dis[i] == -1) continue;  // 不可达的点跳过
            // if (dis[i] < min || (min == dis[i] && cost_ < cost[i])) {
            //  上面的内容是我在处理距离相同时,花费小优先所做的处理,但是参考程序是在40行
            //  判断那里做了一定的修改
            if (dis[i] < min) {
                min = dis[i];
                cost_ = cost[i];
                newP = i;
            }
        }

        mark[newP] = true;
    }
}

int main() {
    int n, m;
    while(scanf("%d%d", &n, &m) != EOF) {
        if (n == 0 && m == 0) break;
        int a, b, d, p;
        while(m--) {
            scanf("%d%d%d%d", &a, &b, &d, &p);
            Edge E;
            E.next = b;
            E.len = d;
            E.cost = p;
            edge[a].push_back(E);
            E.next = a;
            edge[b].push_back(E);
        }
        int s, t;
        scanf("%d%d", &s, &t);
        Dijkstra(n, s);

        printf("%d %d\n", dis[t], cost[t]);
    }
    return 0;
}

你可能感兴趣的:(数据结构,王道机考系列)