【数据结构】最小生成树之Kruskal算法

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;
}

运行结果

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值