通信路线-最小生成树-Kruskal算法-并查集

通信路线-最小生成树-Kruskal算法-并查集

【问题描述】:

若要在n个城市之间建设通信网络,只需要架设n-1条线路即可。如何以最低的经济代价建设这个通信网,是一个网的最小生成树问题。

【基本要求】:

(1)利用克鲁斯卡尔算法求网的最小生成树。
(2)以文本形式输出生成树中各条边以及他们的权值。
(3)输出最低经济代价。

【测试参数】:

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

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

【并查集】:

思路:
初始化每个点,将它们的父亲设为自己,当需要将两个点(或集合)合并时,只需要修改它们的父亲,使得一个点为这个集合中所有点的父亲,如果两个点父亲相同,就说明它们在一个集合中。

举例:
举个便于理解的例子,有一群人,他们彼此不一定认识,那他们怎么区分彼此属于哪个团伙呢?A见到B只需要说自己的队长是XXX,B说自己的队长是XXX,如果队长相同,就是一个队的,当两个队需要合并时,选出一个大队长,再让两个队中的所有人认识大队长,这样遇到别人就可以报出大队长的名字,来确定是不是和对方属于一个队,这就是并查集。

实现:

//初始化父亲数组
void init(MGraph *G) {
    for (int i = 0; i < G->num_vertex; i++)
        father[i] = i;
}

//判断父亲结点是否相同
int same_father(int x, int y) {
    if (find_father(x) == find_father(y))
        return 1;
    else
        return 0;
}

//连接两个节点
void mix_father(int x, int y) {
    int dx = find_father(x);
    int dy = find_father(y);
    if (dx != dy) {
        father[dx] = dy;
    }
}
    for (int i = 0; i < G->num_edge; i++) {
        if (same_father(G->edge[i].start, G->edge[i].end))
            continue;
        mix_father(G->edge[i].start, G->edge[i].end);
        }

【输出样例】:

最小生成树中的边的起点,终点,权值
(361351162252246
最小代价为:12

【代码实现】:

#include "stdio.h"
#include "stdlib.h"

#define maxf 10000 //父亲数组最大容量
#define maxedge 10000//边数组最大容量
int father[maxf];//父亲数组
typedef struct Edge {
    int start;
    int end;
    int cost;
} Edge;//边结构体

typedef struct Graph {
    Edge edge[maxedge];
    int num_vertex;
    int num_edge;
} MGraph;//图结构体

/*
cmp是qsort(快速排序)中最后一个参数的函数,可以自定义,详解见以下链接
https://blog.csdn.net/f_zyj/article/details/51484751
c++中可用sort函数代替qsort
*/
int cmp(const void *a, const void *b) {
    struct Edge *aa = (struct Edge *) a;
    struct Edge *bb = (struct Edge *) b;
    //按权值(cost)升序排序
    return ((aa->cost > bb->cost) ? 1 : -1);
}

//建图
void CreateGraph(MGraph *G) {
    printf("请输入顶点和边的个数:");
    //输入顶点数和边数
    scanf("%d %d", &G->num_vertex, &G->num_edge);
    //读取行尾回车字符
    getchar();
    printf("请输入边的起点,终点,权值:\n");
    //输入图的信息
    for (int i = 0; i < G->num_edge; i++) {
        scanf("%d %d %d", &G->edge[i].start, &G->edge[i].end, &G->edge[i].cost);
    }
    /*
    调用qsort函数按权值(cost)升序排序
     函数原型:
     void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
     base -- 指向要排序的数组的第一个元素的指针。
     nitems -- 由 base 指向的数组中元素的个数。
     size -- 数组中每个元素的大小,以字节为单位。
     compar -- 用来比较两个元素的函数。
    例子:
     qsort(*s, n, sizeof(s[0]), cmp);
     s是一个地址,即参与排序的首地址;
     n是需要排序的数量;
     sizeof(s[0])则是每一个元素占用的空间大小;
     cmp是指向函数的指针,用于确定排序的顺序。
     */
    qsort(G->edge, G->num_edge, sizeof(G->edge[0]), cmp);
/*
    printf("\n排序之后的列表:\n");
    for (int i = 0; i < G->num_edge; i++) {
        printf("%d %d %d\n", G->edge[i].start, G->edge[i].end, G->edge[i].cost);
    }
*/
}

//初始化父亲数组
void init(MGraph *G) {
    for (int i = 0; i < G->num_vertex; i++)
        father[i] = i;
}

//寻找父亲结点
int find_father(int x) {
    if (x == father[x])
        return x;
    else
        return father[x] = find_father(father[x]);
}

//判断父亲结点是否相同
int same_father(int x, int y) {
    if (find_father(x) == find_father(y))
        return 1;
    else
        return 0;
}

//连接两个节点
void mix_father(int x, int y) {
    int dx = find_father(x);
    int dy = find_father(y);
    if (dx != dy) {
        father[dx] = dy;
    }
}

//求解最小代价且输出最小生成树
int min_cost(MGraph *G) {
    int mincost = 0, tp = 0;
    for (int i = 0; i < G->num_edge; i++) {
        if (same_father(G->edge[i].start, G->edge[i].end))
            continue;
        mix_father(G->edge[i].start, G->edge[i].end);

        //输出标题
        if (!tp) {
            printf("最小生成树中的边的起点,终点,权值\n");
            tp = 1;
        }
        printf("(%d,%d) %d\n", G->edge[i].start, G->edge[i].end, G->edge[i].cost);

        mincost += G->edge[i].cost;
    }
    return mincost;
}
//输出最小代价
void print(MGraph *G) {
    int ans = min_cost(G);
    int flag = 0;
    int cnt = find_father(1);//父节点
    for (int i = 2; i <= G->num_vertex; i++) {
        if (find_father(i) != cnt) {
            flag = 1;
            break;
        }
    }
    if (!flag)
        printf("最小代价为:%d\n", ans);
    else
        printf("没有通路");
}
//主函数
int main() {
    MGraph G;
    CreateGraph(&G);
    init(&G);
    print(&G);
    return 0;
}

//测试数据:
/*
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
 */

//输出结果:
/*
最小生成树中的边的起点,终点,权值
(3,6) 1
(3,5) 1
(1,6) 2
(2,5) 2
(2,4) 6
最小代价为:12
 */

【参考资料】:

1、qsort()函数原型:https://www.runoob.com/cprogramming/c-function-qsort.html
2、qsort()函数详解:https://blog.csdn.net/f_zyj/article/details/51484751
3、并查集思路:https://blog.csdn.net/Floatiy/article/details/79424763

  • 8
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值