7-8 哈利·波特的考试,7-9 旅游规划,7-10 公路村村通

之所以把 7-8,7-9,7-10 放到一起,是因为这三个题都是非常经典的图算法,在之前总结的算法笔记中已经提到过,这里只是做一个复习,所以整理到一起。

发现自己的记忆力是真的不行,还是需要勤加努力多认真学习才能把这些算法都掌握住。

7-9 哈利波特的考试

这道题目是想考全局最短路径,Floyd 算法,直接使用 Floyd 算法即可。

关于 Floyd 算法主要是用来求全局最短路径,我们这里给出一份 Floyd算法 的伪代码:

枚举顶点 k 属于 [1, n]
  以顶点 k 作为中介点,枚举所有顶点对 i 和 j
  	如果 dis[i][k] + dis[k][j] < dis[i][j]
  		赋值 dis[i][j] = dis[i][k] + dis[k][j]

但是个人觉得这道题目的难度在于读题 TAT,我读题读了半才读懂是什么意思,看来还是需要提高阅读能力。

完整的解题过程如下:

/*
    Author: Veeupup
    哈利波特的考试
 */
#include <iostream>
#include <cstdio>
#include <cstdint>
#include <climits>
using namespace std;

const int maxn = 110, INF = 1e5;

int n, m;
int dis[maxn][maxn];

// 朴素的 floyd 算法,求全局最短路径
void floyd() {
    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for(int j = 1; j <= n; j++)
                dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}

int main()
{
    freopen("data.txt","r", stdin);    
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
    {   // 初始化点之间的距离,自己到自己距离为 0
        for (int j = 1; j <= n; j++)
        {
            if(i == j) dis[i][j] = 0;
            else dis[i][j] = INF;
        }
    }
    int v1, v2, weight;
    for (int i = 1; i <= m; i++)
    {
        scanf("%d%d%d", &v1, &v2, &weight);
        dis[v1][v2] = dis[v2][v1] = weight;
    }
    
    floyd();

    int ans = INF;
    int cnt = 0;
    for (int i = 1; i <= n; i++)
    {
        int Max = 0;
        for (int j = 1; j <= n; j++)
        {
            if(dis[i][j] == INF) 
            {   // 如果有孤立的结点,那么就无法完成变形
                printf("0");
                return 0;
            }
            Max = max(Max, dis[i][j]);
        }
        if(Max < ans) 
        {
            ans = Max;
            cnt = i;
        }
    }
    printf("%d %d", cnt, ans);
    return 0;
}

7-8 旅游规划

这道题是很经典的具有多个标尺的单源最短路径的变形,我们使用 Dijkstra + DFS 来实现多个标尺的计算,不用在 Dijkstra 中将原算法修改的很复杂。

关于 Dijkstra + DFS 可以参考这里的文章来进行复习。

下面给出完整代码实现:

/*
    Author: Veeupup
    旅游规划
    单源最短路径 Dijkstra,有两个标尺 1. 距离 2. 过路费

    典型的单源最短路径的变形

 */
#include <iostream>
#include <cstdio>
#include <cstdint>
#include <vector>
using namespace std;

const int maxn = 510, INF = 1e4;

int N, M, S, D;
int G[maxn][maxn];  // 保存距离,第一标尺
int cost[maxn][maxn]; // 保存收费金额,图的边权
int dis[maxn], minCost = INF;  // 记录起点到某点的最小距离
bool vis[maxn] = {false};   // 记录是否访问过

vector<int> pre[maxn];  // 保存前一个结点
vector<int> tempPath, path;

void Initial()
{   // 初始化距离和花费最大
    fill(G[0], G[0] + maxn * maxn, INF);
    fill(cost[0], cost[0] + maxn * maxn, INF);
}

void Dijkstra(int S) {
    fill(dis, dis+maxn, INF);   // 初始化到所有点距离无限大
    dis[S] = 0; // 到自己距离为 0
    for (int i = 0; i < N; i++)
    {
        int u = -1, MIN = INF;
        for (int j = 0; j < N; j++)
        {   // 寻找未访问的距离最小值
            if(vis[j] == false && dis[j] < MIN) {
                u = j;
                MIN = dis[j];
            }
        }
        if(u == -1) break;  // 所有可以到达的点都访问过了
        vis[u] = true;  // 设置为已访问
        for (int v = 0; v < N; v++)
        {   // 遍历能从 u 出发到达的所有点
            // 如果经过 u 到达 v 更近,那么就更新 v
            if(dis[u] + G[u][v] < dis[v]) {
                dis[v] = dis[u] + G[u][v];
                pre[v].clear();
                pre[v].push_back(u);    // 经过 u 能够更近的到达 v
            }else if(dis[u] + G[u][v] == dis[v]) {
                // 如果经过 u 到达的时候距离一样远,那么增加到其前面去
                pre[v].push_back(u);
            }   
        }
    }
}

void DFS(int V) {
    if(V == S) {
        tempPath.push_back(S);
        int tempCost = 0;
        int nowId, nextId;
        for (int i = tempPath.size()-1; i > 0; i--)
        {
            nowId = tempPath[i];
            nextId = tempPath[i-1];
            tempCost += cost[nowId][nextId];
        }
        if(tempCost < minCost) {
            minCost = tempCost;
            path = tempPath;
        }
        tempPath.pop_back();
        return;
    }else {
        tempPath.push_back(V);
        for (int i = 0; i < pre[V].size(); i++)
        {
            DFS(pre[V][i]);
        }
        tempPath.pop_back();
    }
}

int main()
{
    freopen("data.txt","r", stdin);
    Initial();
    scanf("%d%d%d%d", &N, &M, &S, &D);
    int u, v;
    for (int i = 0; i < M; i++)
    {   // 读取图信息
        scanf("%d%d", &u, &v);
        scanf("%d%d", &G[u][v], &cost[u][v]);
        G[v][u] = G[u][v];
        cost[v][u] = cost[u][v];
    }
    Dijkstra(S);
    DFS(D);   // 从终点向前回溯
    printf("%d %d\n", dis[D], minCost);

    return 0;

}

7-10 公路村村通

这道题是非常经典的最小生成树算法,推荐使用kruskal算法。

这个题没什么特殊的地方,掌握好最小生成树算法即可,又复习了一遍图算法吧。

完整代码如下:

/*
    Author: Veeupup
    公路村村通

    最小生成树算法:
    Prim(类似Dijkstra) + Krusktal(边贪心)

    这里选择使用 Krusktal 算法来实现
    算法思想很简单

 */
#include <iostream>
#include <cstdio>
#include <cstdint>
#include <algorithm>
using namespace std;

const int maxn = 1010, INF = INT32_MAX;

int N, M;
struct edge
{
    int u, v;
    int cost; // 边权
} E[maxn * 3];

bool cmp(edge a, edge b)
{
    return a.cost < b.cost;
}

// 使用并查集
int father[maxn];
int findFather(int x)
{
    while (x != father[x])
    {
        x = father[x];
    }
    return x;
}

// n 为顶点个数, m 为图的边数
int kruskal(int n, int m)
{
    // ans 为所求边权之和
    int ans = 0, Num_edge = 0;
    for (int i = 1; i <= n; i++)
    {
        father[i] = i;
    } // 并查集初始化
    sort(E, E + m, cmp);
    for (int i = 0; i < m; i++)
    {
        int faU = findFather(E[i].u);
        int faV = findFather(E[i].v);
        if (faU != faV)
        {
            father[faU] = faV;
            ans += E[i].cost;
            Num_edge++;
            if (Num_edge == n - 1)
                break;
        }
    }
    if (Num_edge != n - 1)
        return -1;
    return ans;
}

int main()
{
    freopen("data.txt", "r", stdin);
    scanf("%d%d", &N, &M);
    int u, v, w;
    for (int i = 0; i < M; i++)
    {
        scanf("%d%d%d", &E[i].u, &E[i].v, &E[i].cost);
    }
    int ans = kruskal(N, M);
    printf("%d", ans);
    return 0;
}

希望对大家有帮助。

posted @ 2020-03-30 16:24  南风sa  阅读(142)  评论(0编辑  收藏  举报