图结构专栏——最小生成树之Kruskal算法
一、最小生成树
在含有n个顶点的连通图中选择n-1条边,构成一棵极小连通子图,并使该连通子图中n-1条边上权值之和达到最小,则称其为连通网的最小生成树。
例如,对于上图所示的连通网可以有多棵权值总和不同的生成树,而最后的生成树是最小生成树。
二、Kruskul算法
克鲁斯卡尔(Kruskal)算法,是用来求加权连通图的最小生成树的算法。
- 基本思想:按照权值从小到大的顺序选择n-1条边,并保证这n-1条边不构成回路。
- 具体做法:首先构造一个只含n个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中,并使森林中不产生回路,直至森林变成一棵树为止。
三、Kruskul图解
以上图为例,对Kruskul进行演示
第一步,取边<0,2>
第二步,取边<3,5>
第三步,取边<1,4>
第四步,取边<2,5>
第五步,取边<1,2>
四、如何判断回路
在kruskal算法中有一步很重要的是判断加入这条边会不会产生回路,在这里我们使用的是并查集来帮助判断会不会产生环,关于并查集的详细知识可以查看这篇博客进行学习《并查集以及路径压缩》,这里我们说一说到底怎么样使用并查集判断是否有环。
对于上面这个图,我们使用并查集依次先加入<1,2>,<2,3>和<1,3>
第一步,初始化parents
i | 1 | 2 | 3 |
---|---|---|---|
parents[i] | 1 | 2 | 3 |
第二步,加入边<1,2>,令节点2当父节点
i | 1 | 2 | 3 |
---|---|---|---|
parents[i] | 2 | 2 | 3 |
第三步,加入边<2,3>,令节点3当父节点
i | 1 | 2 | 3 |
---|---|---|---|
parents[i] | 2 | 3 | 3 |
第四步,加入边<1,3>
当需要加入这条边时,发现节点1和节点3有共同的祖先3,这个时候就判断出来如果加入这条边的话图会出现环
五、代码实现
vector<int> parents;//并查集,存储每个节点的父节点/祖先节点
vector<vector<int>> edges;//记录边的信息,每个vector存放一条边的信息,分别是头节点、尾节点和这条边的权值
//nodenum是总结点数
void initialparent(int nodenum)
{
parents.resize(nodenum, -1);
for (int i = 0; i < nodenum; i++)
{
parents[i] = i;
}
}
//寻找node节点的最祖先的节点,同时对该节点的所有祖先做路径压缩
int find(int node)
{
//寻找最祖先的节点
int r = node;
while (parents[r] != r)
{
r = parents[r];
}
int t = node;
//压缩路径
while (t != r)//t是从该节点node开始向上到达祖先节点r的所有节点
{
int temp = parents[t];
parents[t] = r;
t = temp;
}
return r;
}
//对边进行排序,按权值大小从大到小的顺序
void swapEdges()
{
for (int i = 0; i < edges.size(); i++)
{
for (int j = i; j < edges.size(); j++)
{
if (edges[i][2] > edges[j][2])
{
vector<int> t = edges[i];
edges[i] = edges[j];
edges[j] = t;
}
}
}
}
void merge(int x,int y)
{
int r1 = find(x);
int r2 = find(y);
if (r1 == r2)
return;
if (parents[r1] > parents[r2]) {
parents[r1] = r2;
}
else {
parents[r2] = r1;
}
}
//存储图的结构,使用邻接表存储,graph[i]存储与节点i相连的节点
void kruskal(int num)
{
int sumWeight = 0;//权重之和
int u, v;//一条边的头节点和尾节点
initialparent(num);//初始化并查集数组
swapEdges();
for (int i = 0; i < edges.size(); i++)
{
u = edges[i][0];
v = edges[i][1];
if (find(u) != find(v)) { //u和v不在一个集合
printf_s("加入边:%d %d,权值: %d\n", u, v, edges[i][2]);
sumWeight += edges[i][2];
merge(u, v); //把这两个边加入一个集合。
}
}
printf("weight of MST is %d \n", sumWeight);
}