代码随想录算法训练营day57:图07:prim算法精讲;kruskal算法精讲

生成树:

连通图 的 极小连通子图,含有全部n个顶点,n-1条边

       ——添加上一条边,一定会构成环

       ——不是唯一的

       ——可以用DFS/BFS 生成

最小生成树:

在生成树的基础上,要求边权重之和最小

那么如何选择 这 n-1 条边 就是 最小生成树算法的任务所在。

两种方法:

prim

prim算法 是从节点的角度 采用贪心的策略 每次寻找距离 最小生成树最近的节点 并加入到最小生成树中。

prim算法核心就是三步,我称为prim三部曲,大家一定要熟悉这三步,代码相对会好些很多:

构成 生成树区域 vs 非生成树区域,之间有很多边

  1. 第一步,选距离最小的边 / 选距离生成树最近节点
  2. 第二步,最近节点加入生成树
  3. 第三步,更新非生成树节点到生成树的距离(即更新minDist数组)

关键:minDist数组 用来记录 每一个节点距离最小生成树的最近距离。注意下标从1开始,和节点编号对上。

minDist 数组 里的数值初始化为 最大数。

1、prim三部曲,第一步:选距离生成树最近节点

选择距离最小生成树最近的节点,加入到最小生成树,刚开始还没有最小生成树,所以随便选一个节点加入就好(因为每一个节点一定会在最小生成树里,所以随便选一个就好),那我们选择节点1 (符合遍历数组的习惯,第一个遍历的也是节点1)

2、prim三部曲,第二步:最近节点加入生成树

此时 节点1 已经算最小生成树的节点。

3、prim三部曲,第三步:更新非生成树节点到生成树的距离(即更新minDist数组)

接下来,我们要更新所有节点距离最小生成树的距离,如图:

时间复杂度为 O(n^2),其中 n 为节点数量。

如何记录每条边:

二维数组 edge[n][2] : edge[i][0]=起点,edge[i][1]=终点

        有向边必用

一维数组:parent[终点节点编号] = 起点节点编号

        防止反复覆盖

注意点:

1、多基于邻接矩阵

因为邻接表只能从a查找到b,没有办法从b出发遍历找到a——尽管是无向图

而邻接矩阵赋值时:box【i】【j】=box【j】【i】=w

其他没有连接的边全部赋值成最大值(实际上0也可以,但最大值更便于理解)

同时注意邻接矩阵 结点和下标不对应的问题

2、mincost的初始化

长度是n+1——考虑到结点从1开始

离i最近的生成树中结点 记为close[i] = 初始化为1

mincost [i] = box[1][i],如果不存在连接,那就是一个大值

最后,mincost【1】=0!!因为1已经进入生成树了!

3、一共要找到n-1个边,遍历n-1遍

4、三步走

-找最小:mincost相当于记录了每个顶点,到生成树的最小记录,遍历mincost,找到边最小值min_edge和对应的结点min_node

-最小进入生成树:该结点的mincost = 0

mincost【i】 = 0 表明 i 处结点已经在生成树内了

这步相当于找到了一条边,有什么需要输出的从这里走!

-修改其他值:遍历所有和i有连接的顶点(box【i】【1-n】)

只修改还不在生成树里的结点

kruskal

基于边进行考虑,每次放入合法的(=不会形成环的)最小的边

1、对所有边的权重进行排序 qsort cmp

        用结构体

2、选取权重最低的边,共n-1次

        判断加入这条边之后 是否会成为环——并查集 issame

        不会成环:join,count++

        

prim算法精讲

卡码网:53. 寻宝(opens new window)

题目描述:

在世界的某个区域,有一些分散的神秘岛屿,每个岛屿上都有一种珍稀的资源或者宝藏。国王打算在这些岛屿上建公路,方便运输。

不同岛屿之间,路途距离不同,国王希望你可以规划建公路的方案,如何可以以最短的总公路距离将 所有岛屿联通起来。

给定一张地图,其中包括了所有的岛屿,以及它们之间的距离。以最小化公路建设长度,确保可以链接到所有岛屿。

输入描述:

第一行包含两个整数V 和 E,V代表顶点数,E代表边数 。顶点编号是从1到V。例如:V=2,一个有两个顶点,分别是1和2。

接下来共有 E 行,每行三个整数 v1,v2 和 val,v1 和 v2 为边的起点和终点,val代表边的权值。

输出描述:

输出联通所有岛屿的最小路径总距离

#include <math.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

#define maxnode 1000
#define maxedge 1000


int main(){
    int n,e;
    scanf("%d%d",&n,&e);

    int **box = (int **)malloc(sizeof(int*)*(n+1));
    for (int i=1;i<=n;i++){
        box[i]=(int *)malloc(sizeof(int)*(n+1));
        for(int j=1;j<=n;j++) box[i][j]=10001;
    }
    
    for(int i=0;i<e;i++){
        int s,t,w;
        scanf("%d%d%d",&s,&t,&w);
        box[s][t] = w;
        box[t][s] = w;
    }

    //初始化mincost
    int *mincost = (int *)malloc(sizeof(int)*(n+1));

    int *close = (int *)malloc(sizeof(int)*(n+1));

    for(int j=1;j<=n;j++){
        close[j]=1;
        mincost[j]=box[1][j];
    }
    mincost[1]=0;

    int total=0;

    //prim的过程
    for(int i=1;i<=n-1;i++){//一共有n-1条边要放入
        //找最近的 连接边
        int close_edge=10001;
        int close_node;
        for(int j=1;j<=n;j++){
            if(mincost[j]!=0  && mincost[j]<close_edge){//mincost=0 表示这个边已经在生成树区域内了
                close_edge = mincost[j];
                close_node = j;
            }
        }

        //放入已连接区域
        mincost[close_node] = 0;
        total+=close_edge;
        printf("边%d %d已放入\n",close[close_node],close_node);

        //更新权重
        for(int j=1;j<=n;j++){
            if(mincost[j]!=0 && mincost[j]>box[close_node][j]){
                mincost[j] = box[close_node][j];
                close[j]=close_node;
            }
        }
    }

    printf("%d",total);
    return 0;
}

kruskal算法精讲

卡码网:53. 寻宝(opens new window)

题目描述:

在世界的某个区域,有一些分散的神秘岛屿,每个岛屿上都有一种珍稀的资源或者宝藏。国王打算在这些岛屿上建公路,方便运输。

不同岛屿之间,路途距离不同,国王希望你可以规划建公路的方案,如何可以以最短的总公路距离将 所有岛屿联通起来。

给定一张地图,其中包括了所有的岛屿,以及它们之间的距离。以最小化公路建设长度,确保可以链接到所有岛屿。

输入描述:

第一行包含两个整数V 和 E,V代表顶点数,E代表边数 。顶点编号是从1到V。例如:V=2,一个有两个顶点,分别是1和2。

接下来共有 E 行,每行三个整数 v1,v2 和 val,v1 和 v2 为边的起点和终点,val代表边的权值。

输出描述:

输出联通所有岛屿的最小路径总距离

#include <math.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    int s;
    int t;
    int w;
}edge;

int cmp(const void *a,const void *b){
    return ((edge *)a)->w - ((edge *)b)->w;
}

void init(int*father,int e){
    for(int i=1;i<=e;i++) father[i]=i;
}

int find(int*father,int x){
    if(father[x]==x) return x;
    else {
        father[x]=find(father,father[x]);
        return father[x];
    }
}

void join(int *father,int x,int y){
    int a=find(father,x);
    int b=find(father,y);
    father[a]=b;
}

bool issame(int *father,int x,int y){
    int a=find(father,x);
    int b=find(father,y);
    return a==b;
}

int main(){
    int n,e;
    scanf("%d%d",&n,&e);

    edge box[100];
    int father[100];

    int total=0;

    for (int i=0;i<e;i++){
        scanf("%d%d%d",&box[i].s,&box[i].t,&box[i].w);
    }
    qsort(box,e,sizeof(box[0]),cmp);


    int count=0;
    int i=1;
    init(father,e);
    while (count<n-1){
        if(!issame(father,box[i].s,box[i].t)){
            join(father,box[i].s,box[i].t);
            total += box[i].w;
            count ++;
            printf("第%d边:%d %d,w=%d\n",count,box[i].s,box[i].t,box[i].w);
        }
        i++;
    }
    
    printf("%d",total);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值