08-图7 公路村村通

165 阅读3分钟

题目描述

现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使每个村落都有公路连通所需要的最低成本。

输入格式

输入数据包括城镇数目正整数N(≤1000)和候选道路数目M(≤3N);随后的M行对应M条道路,每行给出3个正整数,分别是该条道路直接连通的两个城镇的编号以及该道路改建的预算成本。为简单起见,城镇从1到N编号。

输出格式

输出村村通需要的最低成本。如果输入数据不足以保证畅通,则输出-1,表示需要建设更多公路。

输入样例

6 15
1 2 5
1 3 3
1 4 7
1 5 4
1 6 2
2 3 4
2 4 6
2 5 2
2 6 6
3 4 6
3 5 1
3 6 1
4 5 10
4 6 8
5 6 3

输出样例

12

题解

题意理解:求最小生成树,输出该最小生成树的路径权值之和。

Prim 算法:

求最小生成树可以使用 Prim 算法,该算法的思想是贪心。从选定源点为树根,找到未收入结点里离生成树最近的那个结点然后收入,直到把所有能连通的顶点都收入。算法详解如下。

  • 传入参数为图与初始节点
  • dist[] 数组表示结点到生成树的最短距离,如果 dist == 0 表示结点已被收入
  • dist[] 初始化为各个结点到源点的距离,因为需要计算权重和,就不能初始化为 INF
  • 进入循环
    • 从未收入的结点中找到离生成树最近的结点
    • 如果没找到该结点,退出循环;如果找到,将该结点收入,并且累加 cost
    • 遍历刚刚收入的结点的邻接点,如果因为这个收入的结点使邻接点到生成树的距离减小,更新邻接点的 dist
  • 如果最后发现图不连通,即收入的顶点数目小于图结点总和,说明无法生成最小生成树,因为图本身就不连通

注意:求未收入生成树的距生成树的最小结点算法

最大 N、M 的用例没过就是因为这个算法一开始没写好。

  • 初始化距生成树最近结点下标 min = -1 ,最后返回
  • 初始化到生成树的最近距离 Min = INF
  • 遍历每一个图结点
    • 如果该图结点未被收入(dist[i] > 0)并且这个距离比找到的最近距离小(dist[i] < Min)
    • 更新 Min 与 min
  • 返回 min
#include <stdio.h>
#include <stdlib.h>
#define INF 65535

void BuildGraph(int G[][1001], int N, int M)
{
    int i, j, a, b, c;
    for (i = 1; i <= N; i++) {
        for (j = 1; j <= N; j++) {
            if (i == j) G[i][j] = 0;
            else G[i][j] = G[j][i] = INF;
        }
    }
    for (i = 0; i < M; i++) {
        scanf("%d%d%d", &a, &b, &c);
        G[a][b] = G[b][a] = c;
    }
}

int MinDistV(int G[][1001], int N, int dist[])
{
    int i, min = -1, Min = INF;
    for (i = 1; i <= N; i++) {
        if (dist[i] > 0 && dist[i] < Min) {
            Min = dist[i];
            min = i;
        }
    }
    return min;
}

int Connected(int dist[], int N)
{
    int i, flag = 1;
    for (i = 1; i <= N && flag; i++) {
        if (dist[i] != 0) flag = 0;
    }
    return flag;
}

void Prim(int G[][1001], int N, int S)
{
    int V, W, cost, dist[1001];
    for (V = 1; V <= N; V++) {
        dist[V] = G[S][V];
    }
    cost = 0;
    while (1) {
        V = MinDistV(G, N, dist);
        if (V == -1) break;
        cost += dist[V];
        dist[V] = 0;
        for (W = 1; W <= N; W++) {
            if (dist[W] != 0 && G[V][W] < dist[W]) {
                dist[W] = G[V][W];
            }
        }
    }
    if (!Connected(dist, N)) {
        printf("%d", -1);
    } else {
        printf("%d", cost);
    }

}

int G[1001][1001];

int main()
{
    int N, M;
    scanf("%d%d", &N, &M);
    BuildGraph(G, N, M);
    Prim(G, N, 1);
    return 0;
}

求最小生成树教程中一共讲了两种方法,但本题解只实现了 Prim 算法。

Prim 算法:对稠密图合算

Pasted image 20220821173226.png

Kruskal:对邻接表存的稀疏图合算

Pasted image 20220821173245.png