PTA 08-图7 公路村村通 最小生成树 Prim算法 Kruskal算法 两种算法c语言实现

20 篇文章 2 订阅
9 篇文章 0 订阅

PTA-mooc完整题目解析及AC代码库:PTA(拼题A)-浙江大学中国大学mooc数据结构全AC代码与题目解析(C语言)

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

输入格式:

输入数据包括城镇数目正整数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算法和Kruskal算法。

这里边的个数M≤3N,说明边的数量级与结点个数一致,使用Kruskal算法更好。这里我两种方法都进行了各自实现。

Prim算法

邻接矩阵存储图,再外加一个dist数组存储剩余结点到已生成的树的最短距离,时间复杂度为 O ( N 2 ) O(N^2) O(N2)

该方法较费时,且浪费空间较大。

在这里插入图片描述

Kruskal算法

相比于Prim算法实现稍复杂,但实现该算法不用建图,用一个数组把所有边存储即可。先对边进行排序,然后每次取最小权重的边,利用并查集判断边的两端点是否联通,然后累积权重。时间复杂度为 O ( M l o g M ) O(MlogM) O(MlogM)

在这里插入图片描述


Prim算法:

#include <stdio.h>
#include <stdlib.h>

#define MaxVertexNum 1000
#define INF 65535
int vertexNum;
int graph[MaxVertexNum][MaxVertexNum];
int dist[MaxVertexNum];

void buildGraph();
void initDist();
int findMinDist();
int prim();

int main()
{
    buildGraph();
    printf("%d", prim());

    return 0;
}

void buildGraph()
{
    int edgeNum, i, end1, end2, weight;

    scanf("%d %d", &vertexNum, &edgeNum);
    for (i = 0; i < edgeNum; ++i) {
        scanf("%d %d %d", &end1, &end2, &weight);
        graph[end1 - 1][end2 - 1] = weight;
        graph[end2 - 1][end1 - 1] = weight;
    }
}

void initDist()
{
    int i;
    for (i = 0; i < vertexNum; ++i) {
        dist[i] = INF;
    }
}

int findMinDist()
{
    int minV, i, minDist = INF;

    for (i = 0;i < vertexNum; ++i) {
        if (dist[i] != 0 && dist[i] < minDist) {
            minDist = dist[i];
            minV = i;
        }
    }
    if (minDist < INF)
        return minV;
    else return -1;
}

int prim()
{
    int i, totalWeight, vCount, v;

    initDist();
    for (i = 0; i < vertexNum; ++i) {
        if (graph[0][i])
            dist[i] = graph[0][i];
    }

    totalWeight = 0; vCount = 0;

    dist[0] = 0;
    ++vCount;

    while (1) {
        v = findMinDist();
        if (v == -1)
            break;

        totalWeight += dist[v];
        dist[v] = 0;
        ++vCount;

        for (i = 0; i < vertexNum; ++i)
        if (dist[i] != 0 && graph[v][i] && graph[v][i] < dist[i]) {
            dist[i] = graph[v][i];
        }
    }
    if (vCount < vertexNum)
        totalWeight = -1;
    return totalWeight;
}

Kruskal算法:

#include <stdio.h>
#include <stdlib.h>

#define MaxVertexNum 1000
#define INF 65535
typedef int Vertex;
typedef int WeightType;

typedef struct ENode *PtrToENode;
struct ENode {
    Vertex V1, V2;
    WeightType weight;
};
typedef PtrToENode Edge;
/*-------------------- 顶点并查集定义 --------------------*/
typedef Vertex SetName;     /* 默认用根结点的下标作为集合名称 */
typedef Vertex SetType[MaxVertexNum]; /* 假设集合元素下标从0开始 */

void InitializeVSet( SetType S, int N )
{ /* 初始化并查集 */
    Vertex X;

    for ( X=0; X<N; X++ ) S[X] = -1;
}

void Union( SetType S, SetName Root1, SetName Root2 )
{ /* 这里默认Root1和Root2是不同集合的根结点 */
    /* 保证小集合并入大集合 */
    if ( S[Root2] < S[Root1] ) { /* 如果集合2比较大 */
        S[Root2] += S[Root1];     /* 集合1并入集合2  */
        S[Root1] = Root2;
    }
    else {                         /* 如果集合1比较大 */
        S[Root1] += S[Root2];     /* 集合2并入集合1  */
        S[Root2] = Root1;
    }
}

SetName Find( SetType S, Vertex X )
{ /* 默认集合元素全部初始化为-1 */
    if ( S[X] < 0 ) /* 找到集合的根 */
        return X;
    else
        return S[X] = Find( S, S[X] ); /* 路径压缩 */
}

int CheckCycle( SetType VSet, Vertex V1, Vertex V2 )
{ /* 检查连接V1和V2的边是否在现有的最小生成树子集中构成回路 */
    Vertex Root1, Root2;

    Root1 = Find( VSet, V1 ); /* 得到V1所属的连通集名称 */
    Root2 = Find( VSet, V2 ); /* 得到V2所属的连通集名称 */

    if( Root1==Root2 ) /* 若V1和V2已经连通,则该边不能要 */
        return 0;
    else { /* 否则该边可以被收集,同时将V1和V2并入同一连通集 */
        Union( VSet, Root1, Root2 );
        return 1;
    }
}
/*-------------------- 并查集定义结束 --------------------*/
int vertexNum, edgeNum;
struct ENode edges[MaxVertexNum * 3];

int comp(const void *a, const void *b)
{
    return (*(Edge)a).weight - (*(Edge)b).weight;
}

void initEdges();
int Kruskal();

int main()
{
    printf("%d", Kruskal());

    return 0;
}

void initEdges()
{
    int i;
    scanf("%d %d", &vertexNum, &edgeNum);
    for (i = 0; i < edgeNum; ++i) {
        scanf("%d %d %d", &(edges[i].V1), &(edges[i].V2), &(edges[i].weight));
        --edges[i].V1;  --edges[i].V2;
    }

    qsort(edges, edgeNum, sizeof(struct ENode), comp);
}

int Kruskal()
{
    SetType VSet;
    struct ENode nextEdge;
    int ECount, i; WeightType totalweight;

    initEdges();
    InitializeVSet(VSet, vertexNum);

    ECount = 0; totalweight = 0; i = 0;
    while (ECount < vertexNum - 1) {
        if (i < edgeNum)
            nextEdge = edges[i++];
        else
            break;
        if (CheckCycle(VSet, nextEdge.V1, nextEdge.V2)) {
            totalweight += nextEdge.weight;
            ++ECount;
        }
    }
    if (ECount < vertexNum - 1)
        totalweight = -1;

    return totalweight;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值