Kruskal算法可以形象地称为“加边法”,也就是说,该算法通过处理边,最终得到最小生成树。
存储结构
边集数组:由两个一维数组构成。一个是存储顶点的信息;另一个是存储边的信息。
- 边数组的实现用到结构体!边数组每个数据元素由一条边的起点下(begin)、终点下标(end)和权(weight)组成。
struct Edge{
int begin, end, weight;
};
- 顶点数组使用普通一维数组
基本思想
图中每个顶点各自构成一个连通分量,然后按照边的权值由小到大的顺序.依次考察边集E中的各条边。
若被考察边的两个项点属于两个不同的连通分量则将此边加人到TE中,同时把两个连通分量连接为一个连通分量;若被考察边的两个顶点属于同一个连通分量,则舍去此边,以免造成回路,如此下去,当T中的连通分量个数为1时,此连通分量便为G的一棵最小生成树。
显然,该算法实现中需要实现对边按权值从小到大排序。
我们还可以发现,算法的实现过程中需要考查边的两顶点是否属于同一连通分量,其实就是再看边的两个顶点是否属于同一个集合。显然,该过程要用并查集!!
代码实现
克鲁斯卡尔算法操作对象是图,因此使用该算法必须有图!
如上文所言,图的存储使用边集数组。结构体声明为:
struct Edge{
int begin,end,weight;
};
建图及后续算法的实现通过面向对象的方法完成。
以数组元素为字符型为例,类的声明:
class EdgeGraph
{
public:
EdgeGraph(DataType a[ ], int n, int e); //建立n个顶点e条边的图
~EdgeGraph( ); //析构函数
void Kruskal( ); //Kruskal算法
private:
int FindRoot(int parent[ ], int v); //求顶点v所在集合的根
int vertex[MaxVertex]; //存储顶点的一维数组
EdgeType edge[MaxEdge]; //边集数组
int vertexNum, edgeNum; //顶点和边的数目
};
构造函数
由于使用到了边集数组,不同于普通的邻接矩阵,所以,建图时边两端的端点也需要记录!
由于克鲁斯卡尔算法根据边递增的顺序遍历,因此应对边进行排序!
在这里,我们使用C++ algorithm库中的sort函数排序~
由于对结构数组中的weight按从小到大排序,因此需要自定义比较函数
int cmpn(Edge a,Edge b){
return a.weight<b.weight;
}
之后需要在构造函数中调用sort函数
EdgeGraph:: EdgeGraph(char a[ ], int n, int e)
{
int i, j, k, w;
vertexNum = n; edgeNum = e;
for (i = 0; i < vertexNum; i++)
vertex[i] = a[i];
for (k = 0; k < edgeNum; k++)
{
cout << "输入边依附的两个顶点的编号,以及边上的权值:";
cin >> i >> j >> w;
edge[k].from = i; edge[k].to = j; //记录边的端点
edge[k].weight = w; //记录权值
}
sort(edge,edge+EdgeNum,cmpn);
}
核心代码
克鲁斯卡尔算法的核心用到了并查集的方法。
在克鲁斯卡尔函数内首先对全部顶点的双亲初始化-1,之后调用“查”函数,依次比较每条边两顶点的根。若两顶点不同根,合并两元素的集合(改变两集合代表元素的双亲关系,将2个连通分量合并为1个)。当连通分量个数为1时,所有点都属于一个集合,此时,此连通分量便为G的一棵最小生成树。
void EdgeGraph:: Kruskal( )
{
int num = 0, i, v1, v2;
int parent[vertexNum]; //双亲表示法存储集合
for (i = 0; i < vertexNum; i++)
parent[i] = -1; //初始化n个连通分量
int num = 0; //迭代计数
for ( i = 0; num < vertexNum - 1; i++)
//依次考察边
{
v1 = FindRoot(parent, edge[i].from);
v2 = FindRoot(parent, edge[i].to);
if (v1 != v2) { //位于不同的集合
cout << "(" << edge[i].from << "," << edge[i].to << ")" << edge[i].weight << endl;
parent[v2] = v1; //合并集合
num++;
}
}
}
“查”函数与并查集中的查找函数类似
int EdgeGraph :: FindRoot(int parent[ ], int v) //求顶点v所在集合的根
{
int t = v;
while (parent[t] != -1) //求顶点t的双亲,直到根
t = parent[t];
return t;
}
实例
测试无向图
边依次为 (1 4 12)(2 3 17)(0 5 19) (2 5 25)(3 5 25)(4 5 26)(0 1 34)(3 4 38)(0 2 46)
源代码
#include <iostream>
#include<algorithm>
using namespace std;
const int MaxVertex = 10; //图中最多顶点数
const int MaxEdge = 100; //图中最多边数
struct Edge{
int begin,end,weight;
};
int cmpn(Edge a,Edge b){
return a.weight<b.weight;
}
class MGraph{
public:
MGraph(char a[],int n,int e);
~MGraph(){}
void Kruskal( );
private:
int FindRoot(int p[ ], int v);
int VertexNum,EdgeNum;
Edge edge[MaxEdge];
char vertex[MaxVertex];
};
MGraph::MGraph(char a[],int n,int e){
VertexNum = n; EdgeNum = e;
int i, j, k, w;
for (i = 0; i < VertexNum; i++)
vertex[i] = a[i];
for (k = 0; k < EdgeNum; k++)
{
cout << "输入边依附的两个顶点及边上的权值:";
cin >> i >> j >> w; //输入边依附的两个顶点的编号
edge[k].begin = i; edge[k].end = j; edge[k].weight = w;
}
sort(edge,edge+EdgeNum,cmpn);
}
void MGraph::Kruskal(){
int parent[MaxVertex];
int i,j,k;
for(i=0;i<VertexNum;i++) parent[i]=-1;
int v1,v2;
for(k=1,i=0;k<VertexNum;i++){
//找代表元素
v1=FindRoot(parent,edge[i].begin);
v2=FindRoot(parent,edge[i].end);
//合并集合
if(v1!=v2) {
cout<<"("<<edge[i].begin<<","<<edge[i].end<<")"<<"->"<<edge[i].weight<<endl;
parent[v1]=v2;
k++;
}
}
}
int MGraph::FindRoot(int p[ ], int v){
int index=v;
while(p[index]!=-1) index=p[index];
return index;
}
int main( ){
char ch[ ]={'A','B','C','D','E','F'};
MGraph G(ch, 6, 9);
G.Kruskal( );
return 0;
}