生成树:
连通图 的 极小连通子图,含有全部n个顶点,n-1条边
——添加上一条边,一定会构成环
——不是唯一的
——可以用DFS/BFS 生成
最小生成树:
在生成树的基础上,要求边权重之和最小
那么如何选择 这 n-1 条边 就是 最小生成树算法的任务所在。
两种方法:
prim
prim算法 是从节点的角度 采用贪心的策略 每次寻找距离 最小生成树最近的节点 并加入到最小生成树中。
prim算法核心就是三步,我称为prim三部曲,大家一定要熟悉这三步,代码相对会好些很多:
构成 生成树区域 vs 非生成树区域,之间有很多边
- 第一步,选距离最小的边 / 选距离生成树最近节点
- 第二步,最近节点加入生成树
- 第三步,更新非生成树节点到生成树的距离(即更新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算法精讲
题目描述:
在世界的某个区域,有一些分散的神秘岛屿,每个岛屿上都有一种珍稀的资源或者宝藏。国王打算在这些岛屿上建公路,方便运输。
不同岛屿之间,路途距离不同,国王希望你可以规划建公路的方案,如何可以以最短的总公路距离将 所有岛屿联通起来。
给定一张地图,其中包括了所有的岛屿,以及它们之间的距离。以最小化公路建设长度,确保可以链接到所有岛屿。
输入描述:
第一行包含两个整数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算法精讲
题目描述:
在世界的某个区域,有一些分散的神秘岛屿,每个岛屿上都有一种珍稀的资源或者宝藏。国王打算在这些岛屿上建公路,方便运输。
不同岛屿之间,路途距离不同,国王希望你可以规划建公路的方案,如何可以以最短的总公路距离将 所有岛屿联通起来。
给定一张地图,其中包括了所有的岛屿,以及它们之间的距离。以最小化公路建设长度,确保可以链接到所有岛屿。
输入描述:
第一行包含两个整数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;
}