最小生成树-prim与kruskal算法讲解

参考链接

前提:

构造最小生成树有许多算法,但大多数算法都利用了最小生成树的下列性质: 假设G=(V,E)是一个带权连通无向图,U是顶点集V的一个非空子集。若(u,v)是一条具有最小权值的边,其中u€U,v€V-U,则B存在一棵包含边(u,v)的最小生成树。
基于该性质的最小生成树算法主要有prim算法和kruskal算法,他们都基于贪心算法的策略。
下面介绍一个通用的最小生成树算法:

GENERIC_MST(G){
    T=NULL;
    while T未形成一棵生成树;
        do 找到一条最小代价边(u,v) 并且加入T后不会产生回路;
            T=T U (u,v);

Prim(普里姆)算法

步骤:
设G=(V,E)是连通权图,V={1,2,3….n};
构造G的最小生成树的Prim算法的基本思想是:首先设置S={1},然后,只要S是V的真子集,就如下的贪心选择:选取满足条件i属于S,j属于V-S。且c[i][j]最小的边,并将顶点j添加到S中。这个过程一直进行到S=V时为止。在这个过程中选取的所有边恰好构成G的一棵最小生成树:
按照上面的Prim算法的具体过程

1: 首先我们应该定义一个集合S用来存源点这里我们将起点定位1,所以开始时S={1};定义另一个集合B用来存集合V中除去集合S中的点之外的所有点;

2: 定义一个集合dis用来存集合S中的点能够到达集合B中的点的边长;初始时dis具体情况如下:
在这里插入图片描述
如上表所示,集合S到集合B之间距离最最近的点是顶点3;所以我们将顶点3放入集合S此时调整集合dis值如下表:
在这里插入图片描述
从表中我们可以看出,此时集合S中的点到集合B中的点距离最近的是边:dis(3,6),所以我们将顶点6放到集合S中,此时集合S中包含的点为S={1,3,6};再次调整集合dis,得下表:
在这里插入图片描述
从表中我们可以看出,此时集合S中的点到集合B中的点距离最近的是边是:dis(6,4),所以我们将顶点4放到集合S中;此时集合S中包含的点为S={1,3,6,4};再次调整集合dis,得下表:
在这里插入图片描述
从表中我们可以看出,此时集合S中的点到集合V-S中的点距离最近的边是:dis(3,2),所以我们将顶点2放到集合S中;此时集合S中包含的点为S={1,3,6,4,2};再次调整集合dis,得下表:
在这里插入图片描述
从表中我们可以看出,此时集合S中的点到集合V-S中的点距离最近的边是:dis(2,5),所以我们将顶点5放到集合S中;此时集合S中包含的点为S={1,3,6,4,2,5};已经包含了集合V中的所有元素,此时我们所得到的子图就是我们所要求解的最小生成树。如下图所示:
在这里插入图片描述

prim算法具体代码

//边
struct Edge{
    int start;
    int end;
    int length;
};
 
//需要传入图邻接矩阵graph,点的个数length
Edge* Fun(int **graph,int length){
    //length个点,(length-1)条边
    int* V=(int*)calloc(length,sizeof(int));
    Edge* E=(Edge*)malloc((length-1)*sizeof(Edge));
    
    //先设起始点,置1说明入队列
    V[0]=1;
    
    for(int i=0;i<length-1;i++){
        //寻找满足要求的最短边
        for (int j=0,min=INT_MAX; j<length; j++) {
            //判断当前点是否在点队列
            if(V[j]>0){
                for(int k=0;k<length;k++){
                    if(V[k]==0&&graph[j][k]<min&&graph[j][k]>0){
                        min=graph[j][k];
                        E[i].start=j;
                        E[i].end=k;
                        E[i].length=min;
                    }
                }
            }
        }
        //把新点加入队列
        V[E[i].end]=1;
    }
    
    free(V);
    return E;
}


完整prim算法java代码

Kruskal算法

步骤:
1).记Graph中有v个顶点。e个边
2).新建图Graphnew,Graphnew中拥有原图中同样的e个顶点,但没有边
3).将原图Graph中全部e个边按权值从小到大排序
4).循环:从权值最小的边開始遍历每条边 直至图Graph中全部的节点都在同一个连通分量 if 这条边连接的两个节点于图Graphnew中不在同一个连通分量中 增加这条边到图Graphnew中
例子举例
完整代码实现及测试数据:

#include "stdio.h"
#include "stdlib.h"
#define MAX_VERtEX_NUM 20
#define VertexType int
typedef struct edge{
    VertexType initial;
    VertexType end;
    VertexType weight;
}edge[MAX_VERtEX_NUM];
//定义辅助数组
typedef struct {
    VertexType value;//顶点数据
    int sign;//每个顶点所属的集合
}assist[MAX_VERtEX_NUM];

assist assists;

//qsort排序函数中使用,使edges结构体中的边按照权值大小升序排序
int cmp(const void *a,const void*b){
    return  ((struct edge*)a)->weight-((struct edge*)b)->weight;
}
//初始化连通网
void CreateUDN(edge *edges,int *vexnum,int *arcnum){
    printf("输入连通网的边数:\n");
    scanf("%d %d",&(*vexnum),&(*arcnum));
    printf("输入连通网的顶点:\n");
    for (int i=0; i<(*vexnum); i++) {
        scanf("%d",&(assists[i].value));
        assists[i].sign=i;
    }
    printf("输入各边的起始点和终点及权重:\n");
    for (int i=0 ; i<(*arcnum); i++) {
        scanf("%d,%d,%d",&(*edges)[i].initial,&(*edges)[i].end,&(*edges)[i].weight);
    }
}
//在assists数组中找到顶点point对应的位置下标
int Locatevex(int vexnum,int point){
    for (int i=0; i<vexnum; i++) {
        if (assists[i].value==point) {
            return i;
        }
    }
    return -1;
}
int main(){
   
    int arcnum,vexnum;
    edge edges;
    CreateUDN(&edges,&vexnum,&arcnum);
    //对连通网中的所有边进行升序排序,结果仍保存在edges数组中
    qsort(edges, arcnum, sizeof(edges[0]), cmp);
    //创建一个空的结构体数组,用于存放最小生成树
    edge minTree;
    //设置一个用于记录最小生成树中边的数量的常量
    int num=0;
    //遍历所有的边
    for (int i=0; i<arcnum; i++) {
        //找到边的起始顶点和结束顶点在数组assists中的位置
        int initial=Locatevex(vexnum, edges[i].initial);
        int end=Locatevex(vexnum, edges[i].end);
        //如果顶点位置存在且顶点的标记不同,说明不在一个集合中,不会产生回路
        if (initial!=-1&& end!=-1&&assists[initial].sign!=assists[end].sign) {
            //记录该边,作为最小生成树的组成部分
            minTree[num]=edges[i];
            //计数+1
            num++;
            //将新加入生成树的顶点标记全不更改为一样的
            for (int k=0; k<vexnum; k++) {
                if (assists[k].sign==assists[end].sign) {
                    assists[k].sign=assists[initial].sign;
                }
            }
            //如果选择的边的数量和顶点数相差1,证明最小生成树已经形成,退出循环
            if (num==vexnum-1) {
                break;
            }
        }
    }
    //输出语句
    for (int i=0; i<vexnum-1; i++) {
        printf("%d,%d\n",minTree[i].initial,minTree[i].end);
    }
    return 0;
}

测试数据输入连通网的边数: 6 10 输入连通网的顶点: 1 2 3 4 5 6 输入各边的起始点和终点及权重: 1,2,6 1,3,1 1,4,5 2,3,5 2,5,3 3,4,5 3,5,6 3,6,4 4,6,2 5,6,6 1,3 4,6 2,5 3,6 2,3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰糖葫芦五加皮耶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值