【PAT】A1003. Emergency(最短路径算法)

时间:2021-08-27 18:45:20

【PAT】advanced_1003. Emergency(最短路径算法)

@(PAT)

首先利用这道题要复习一下最短路径算法,经典的最短路径算法有两个:Floyd算法和Dijkstra算法。

Floyd算法

关于Floyd算法,基本思路如下:
1. 使用d来存储图,d[i][j]表示从i到j路径的长度,设为无穷大表示路径不存在。使用path存储路径,path[i][j]表示从i到j的最后经过的节点。
2. 在节点A和B之间,寻找是否存在一个中间节点C,使得A到C,C到B的路径比直接A到B的路径短,如果存在,就更新d和path。
3. 使用3层循环,最外层的是中间点的循环,内两层循环为节点A和B的循环,意思是每个节点当作中间节点,然后看每两个时间是否存在更短的路径。
4. 因为遍历了每种情况,所以能够解决节点A和B之间存在2个中间节点使路径更短的情况,因为随着循环的进行,每次解决存在一个中间节点的情况,最终A和B存在N个中间节点的情况都会成为A和B存在一个中间节点C使得路径最短的情况。

另外关于floyd算法,一个需要注意的地方就是它是全局的,会把d的所有数据都更新为最短的路径。

核心代码如下(3层循环):

    for (int k= 0; k< n; k++) {
        for (int i= 0; i< n; i++) {
            for (int j= 0; j< n; j++) {
                if (i== j) continue;
                if (d[i][k]+ d[k][j]< d[i][j]) {
                    d[i][j]= d[i][k]+ d[k][j];
                    path[i][j]= path[i][k];
                }
            }
        }
    }

Dijkstra算法

该算法非常经典,以前没有好好研究,在计算机网络中也有用到,比较重要,现在趁这道题好好研究该算法。
参考blog,结合过程图理解过程:
http://www.cnblogs.com/skywang12345/p/3711512.html
https://www.61mon.com/index.php/archives/194/

与floyd算法不同,dijkstra算法只能找到给定初始点到图上其他的每个点的距离。

理解了过程后,从代码出发,该算法主要有以下方面:
1. 用二维数组mat存储图,如果i和j之间不存在边,则mat[i][j]设置为无穷大。
2. 算法需要借助3个数组:存储路径的prev、存储最短路径长度的dist和当前是否已经找到初始点与该点的最短距离flag。需要将它们初始化,prev和flag初始化为0,dist初始化为mat中初始点C1到其他点的初始距离。
3. 对初始点数据进行初始化,flag为1,dist为0。
4. 进行n- 1循环(n为点数),每次循环都找到初始点C1到另外一个点的最短路径。
5. 每次n-1循环中,遍历n次,在没找过的点(flag== 0)中,寻找dist最短的点K。找到之后,将之记录为已经找过(flag== 1,加入在已经寻找过的集合中)。然后再遍历n次,对与之不相连的点(mat[k][j]>= MY_MAX)不作处理,对与之相连的点P,如果寻找到的最短dist(初始点C1到K的距离)加上边K到P的边的长度比初始点到P的距离(dist[j])短,则将C1到P更新为最短dist(初始点C1到K的距离)加上边K到P的边的长度,并更新prev数组记录路径。
6. 最后得到的flag应该全部为1,dist存储的是初始点C1到各点的最短路径,prev存储的是C1到目标点M的最短路径的M之前的一点。

结合本题的数据和输入,初步使用dijkstra算法的code如下:

#include <iostream>
#include <vector>

using namespace std;

#define MY_MAX 99999

int main() {
    int n, m, c1, c2;
    scanf("%d %d %d %d", &n, &m, &c1, &c2);
    vector<int> num_RT;
    for (int i= 0; i< n; i++) {
        int temp;
        scanf("%d", &temp);
        num_RT.push_back(temp);
    }
    // adjacency matrix
    int mat[500][500];
    for (int i= 0; i< n; i++) {
        for (int j= 0; j< n; j++) {
            mat[i][j]= MY_MAX;
        }
    }
    for (int i= 0; i< m; i++) {
        int x, y, d;
        scanf("%d %d %d", &x, &y, &d);
        mat[x][y]= d;
        mat[y][x]= d;
    }
    int prev[500];
    int dist[500];
    int flag[500];
    // initialize
    for (int i= 0; i< n; i++) {
        flag[i]= 0;
        prev[i]= 0;
        dist[i]= mat[c1][i];
    }
    flag[c1]= 1;
    dist[c1]= 0;
    int min;
    int k= 0;
    // loop for n-1 times
    for (int i= 1; i< n; i++) {
        min= MY_MAX;
        for (int j= 0; j< n; j++) {
            if (flag[j]== 0&& dist[j]< min) {
                min= dist[j];
                k= j;
            }
        }
        flag[k]= 1;
        int temp;
        for (int j= 0; j< n; j++) {
            if (mat[k][j]>= MY_MAX) {
                temp= MY_MAX;
            } else {
                temp= min+ mat[k][j];
            }
            if (flag[j]== 0&& temp< dist[j]) {
                dist[j]= temp;
                prev[j]= k;
            }
        }
    }
    for (int i= 0; i< n; i++) {
        cout<< dist[i]<< endl;
    }
}

现在回到题目上来。题目要求输出的是c1和c2之间的最短路径的数目和最短路径的最大权重和。基于基本的dijkstra算法,思路如下:
1. 在第一层循环中,找到最小边后,再进行一次遍历的时候,需要更改原来基本的dijkstra算法。
2. 如果找到了新的最短路径,则更新最短路径,同时该点的最短路径数目和前驱的最短路径数目是相同的(因为新加的边已经是最短的路径)同时增加新增点的权值
3. 如果找到了相等的最短路径,则到该点的最短路径数目为原来找到的数目和新找到的数目和,同时取较大的权值和。

我的AC代码如下:

#include <iostream>
#include <vector>

using namespace std;

#define INF 99999

int main() {
    int n, m, start, end;
    scanf("%d%d%d%d", &n, &m, &start, &end);
    vector<int> weight;
    for (int i= 0; i< n; i++) {
        int temp;
        scanf("%d", &temp);
        weight.push_back(temp);
    }
    // initialize adjacent matrix
    vector< vector<int> > mat;
    vector<int> tempv;
    for (int i= 0; i< n; i++) {
        tempv.push_back(INF);
    }
    for (int i= 0; i< n; i++) {
        mat.push_back(tempv);
    }
    // save the edge of the graph
    for (int i= 0; i< m; i++) {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        mat[a][b]= c;
        mat[b][a]= c;
    }
    // use count to save the num of shortest paths
    vector<int> count;
    // use maxAmt to save the max amount of teams
    vector<int> maxAmt;
    // use visited to save if the point if visited by Dijkstra Algorithm
    vector<bool> visited;
    // use d to save the shortest distance
    vector<int> d;
    // initialize
    for (int i= 0; i< n; i++) {
        count.push_back(0);
        maxAmt.push_back(weight[i]);
        visited.push_back(false);
        d.push_back(mat[start][i]);
    }
    // the adjacent points of start
    for (int i= 0; i< n; i++) {
        if (d[i]< INF) {
            count[i]= 1;
            maxAmt[i]+= weight[start];
        }
    }
    // the start point
    visited[start]= true;
    d[start]= 0;
    count[start]= 1;
    // begin Dijkstra loop
    for (int i= 1; i< n; i++) {
        int min_d= INF;
        int min_i= 0;
        for (int j= 0; j< n; j++) {
            if (!visited[j]&& d[j]< min_d) {
                min_d= d[j];
                min_i= j;
            }
        }
        visited[min_i]= true;
        for (int j= 0; j< n; j++) {
            if (!visited[j]) {
                if (min_d+ mat[min_i][j]< d[j]) {
                    d[j]= min_d+ mat[min_i][j];
                    count[j]= count[min_i];
                    maxAmt[j]= maxAmt[min_i]+ weight[j];
                } else if (min_d+ mat[min_i][j]== d[j]) {
                    count[j]+= count[min_i];
                    maxAmt[j]= max(maxAmt[j], maxAmt[min_i]+ weight[j]);
                }
            }
        }
    }
    printf("%d %d", count[end], maxAmt[end]);
}

注意dijkstra算法中,循环内的第一次循环是找到最小路径的点的,找到该点后就以该点为依据,找和这个点相邻的点,然后根据是比d更小还是相等而采取相应的操作。

该代码研究了好一会儿才写出来的,不太优雅,希望慢慢消化以后更新一个更加优雅的版本。另外,最短路径算法是经常被用来考察的点,务必要好好消化!