最小生成树kruskal算法(贪心+并查集+堆优化)

Kruskal算法是一种求解最小生成树的贪心算法,通过选择最小权值边并避免形成环路来逐步构建连通图。算法首先按边权值排序,然后使用并查集判断新加入的边是否会形成环路。如果不会,就将边加入结果中。并查集在此过程中起关键作用,用于快速判断元素是否属于同一集合,以防止形成环路。通过并查集的搜索和合并操作,可以高效地实现Kruskal算法,优化后的算法时间复杂度可达O(n log n)。

kruskal算法

克鲁斯卡尔算法的基本思想是以边为主导地位,始终选择当前可用(所选的边不能构成回路)的最小权植边。所以Kruskal算法的第一步是给所有的边按照从小到大的顺序排序。这一步可以直接使用库函数qsort或者sort。接下来从小到大依次考察每一条边(u,v)。

具体实现过程如下:

<1> 设一个有n个顶点的连通网络为G(V,E),最初先构造一个只有n个顶点,没有边的非连通图T={V,空},图中每个顶点自成一格连通分量。

<2> 在E中选择一条具有最小权植的边时,若该边的两个顶点落在不同的连通分量上,则将此边加入到T中;否则,即这条边的两个顶点落到同一连通分量上,则将此边舍去(此后永不选用这条边),重新选择一条权植最小的边。

<3> 如此重复下去,直到所有顶点在同一连通分量上为止。

简单来说就是挑最小边,加入当前的“树”中,而挑最小边可以遍历,时间复杂度是O(n),如果用最小堆时间复杂度可以达到o(logn)

并入当前树中又不会形成环路解决方法当然就是用并查集

并查集(Union-Find set)这个数据结构可以方便快速的解决这个问题。基本的处理思想是:初始时把每个对象看作是一个单元素集合;然后依次按顺序读入联通边,将连通边中的两个元素合并。在此过程中将重复使用一个搜索(Findroot)运算,确定一个集合在那个集合中。当读入一个连通边(u,v)时,先判断u和v是否在同一个集合中,如果是则不用合并;如果不是,则用一个合并(Union)运算把u、v所在集合合并,使得这两个集合中的任意两个元素都连通。因此并查集在处理时,主要用到搜索合并两个运算。

为了方便并查集的描述与实现,通常把先后加入到一个集合中的元素表示成一个树结构,并用根结点的序号来表示这个集合。因此定义一个uf[n]的数组,uf[i]中存放的就是结点i所在的树中结点i的父亲节点的序号。例如,如果uf[4]=5,就是说4号结点的父亲结点是5号结点。约定:如果i的父结点(即uf[i])是负数,则表示结点i就是它所在的集合的根结点,因为集合中没有结点的序号是负的;并且用负数的绝对值作为这个集合中所含结点的个数。例如,如uf[7]=-4,说明7号结点就是它所在集合的根结点,这个集合有四个元素。初始时结点的值为-1(每个结点都是根结点,只包含它自己一个元素)。


#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std;

#define inf 99999
#define maxsize 10


typedef struct Graph//定义图 a,b,代表边的两顶点,w表权值 
{
	int a,b;
	int w;
}G;

int nv,ne;//图的点、边 
int uf[maxsize];//并查集数组 
G minheap[maxsize];//最小堆数组 
int size=0;



void insert(G t)//向最小堆中插入元素 
{
	int i=++size;
	for(;minheap[i/2].w>t.w;i/=2)
		minheap[i]=minheap[i/2];
	minheap[i]=t;
}

void down()//下滤 
{
	int ch;
	for(int pa=1;pa*2<=size;pa=ch)
	{
		ch=pa*2;
		if(ch!=size&&minheap[ch].w>minheap[ch+1].w)
			ch=ch+1;
		if(minheap[ch].w<minheap[pa].w)
			swap(minheap[ch],minheap[pa]);
		else
			break;
	}
}

G del()//从最小堆中删除元素 
{
	if(size>0)
	{
		G mx=minheap[1];
		minheap[1]=minheap[size--];
		down();
		return mx;
	}
}

int findroot(int u)//发现根 
{
	int i;
	for( i=u;uf[i]!=-1;i=uf[i]);
	return i;
}

void Create()//创建最小堆 
{
	int a,b,weigh,k=0;
	G g;
	for(int i=1;i<=ne;i++)//直接输入图的边及权值 
	{
		cin>>a>>b>>weigh;
		g.a=a;
		g.b=b;
		g.w=weigh;
		insert(g);//放入最小堆 
	}
	for(int i=0;i<=nv;i++)//初始化并查集 
		uf[i]=-1;
	minheap[0].a=minheap[0].b=0;
	minheap[0].w=-1;
}

int  kruskal()
{
	int a,b,sum=0;
	G tmp;
	for(int i=1;i<=ne;i++)
	{
		tmp=del();//每次从最小堆中找到一个最小的边保存并删除
		a=findroot(tmp.a);//找到a的根 
		b=findroot(tmp.b);//找到b的根 
		if(a!=b)//合并相当于union函数
		{
			uf[b]=a;
			sum+=tmp.w;
		}
	}
	return sum;//返回最小生成树的总权值 
}

int main()
{
	cin>>nv>>ne;
	Create();
	cout<<kruskal()<<endl;
	return 0;
}





评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值