最小生成树的kruskal算法

这里我将用一道题的形式来详细的讲一下kruskal算法(非常详细,建议收藏)

                                        1348:【例4-9】城市公交网建设问题


                                                       时间限制: 1000 ms         内存限制: 65536 KB
                                                                    提交数: 7677     通过数: 3195

【题目描述】

有一张城市地图,图中的顶点为城市,无向边代表两个城市间的连通关系,边上的权为在这两个城市之间修建高速公路的造价,研究后发现,这个地图有一个特点,即任一对城市都是连通的。现在的问题是,要修建若干高速公路把所有城市联系起来,问如何设计可使得工程的总造价最少?

【输入】

n(城市数,1<≤n≤100)

e(边数)

以下e行,每行3个数i,j,wiji,j,wij,表示在城市i,j之间修建高速公路的造价。

【输出】

n-1行,每行为两个城市的序号,表明这两个城市间建一条高速公路。

【输入样例】

5 8
1 2 2
2 5 9
5 4 7
4 1 10
1 3 12
4 3 6
5 3 3
2 3 8

【输出样例】

1  2
2  3
3  4
3  5

这是一道经典的最小生成树的题,今天我要写一篇我自认为十分详细的文章

最小生成树是啥?

一个有n个点的图,边一定是大于n-1条的,我解释一下啊(萌新礼包)

这是个有四个节点的图吧!如果我们要连接他,只能每两个点来连接,所以,答案显而易见:

所以这不就是三条边嘛!(希望我讲的清楚)

最小生成树,就是在一个边数在n-1以上的图选出n-1条边,使其连接到所有的n个点的权值之和最小。

我们来讲一下Kruskal(克鲁斯卡尔)算法是一种和并查集完美结合的最小生成树的算法。

kruakal算法:

Kruskal算法将一个连通块当作一个集合。Kruskal首先将所有的边按从大到小顺序排序(一般使用快排),并认为每一个点都是独立的,分属于n个独立的集合。

回到题:

我们要怎样才能让总工程量最小?

我的第一个思路就是图论,弗洛伊德试试,当然我说了要讲kruskal肯定不会讲弗洛伊德的,然后就写了一堆无用的东西……

当我正在准备测试样例的时候,我看到了这是最小生成树的题,其实我早就看到了,只是想冒个险(⊙﹏⊙)

最后我终于回归正道,其实早就该用Kruskal算法来做:

重点来了!!!

第一步:

        我们要建立一个结构体数组:

struct tree{
	int s,e,w;
}a[101],s,k;

        s表示的是起点,e表示的是终点,w表示的是花费(权值)

        我们再建立一个小根堆:

priority_queue<tree,vector<tree> >q

        这个小根堆需要时时刻刻的把权值都从大到小排序,所以:

bool operator<(tree A,tree B)
{
	return A.w>B.w;
}

        这里的"<"是重载运算符 ,需要把a和b的权值从大到小排序 

第二步:

        第二步是并查集的基本操作,懂的可以忽略 

        并查集的基本操作:

        如果f[x]的就是x,那么就返回父节点,最后继续查找f[x]的父节点(注释就不删了,对应一下)

int find(int x)
{
	if(f[x]==x)//找到f[x]的父节点 
		return x;//返回父节点 
	return f[x]=find(f[x]);//继续查找f[x]的父节点 
}

        合并的函数:

        第一

                将x与y的父节点找出来

        第二

                如果他们的父亲不相同,就说明他们之间没有关系

                但是我们要合并他们呀,所以就把父节点的信息保存好 

void fun(int x,int y)
{
	x=find(x);//找到x的父节点 
	y=find(y);//找到y的父节点 
	if(x!=y)//x与y不相等,说明不是同一个点(或者说明x与y没有关系,不能连通)
		f[y]=x;//保存父节点信息
}

第三步:

        终于到写Kruskal算法的时间了!

        在kruskal的开头,当然还是要标记父节点的!

for(int i=1;i<=n;i++)
		f[i]=i;

        我们真聪明,代码已经完成了20%了,其实只有2/11

        我们用k来记录小根堆的堆顶元素

        将k的起点和终点要合并起来

        但是,你有没有想过,等会儿怎么输出呢?

        那我们的a数组就有用了,用来记录路径

        我们要吧k的信息存到a数组里面去

        但a数组的下标怎么办呢?

        肯定只能用再用一个变量t

        所以,上面合并的代码变为:

void fun(int x,int y)
{
	x=find(x);//找到x的父节点 
	y=find(y);//找到y的父节点 
	if(x!=y)//x与y不相等,说明不是同一个点(或者说明x与y没有关系,不能连通)
    {
        f[y]=x;//保存父节点信息
        a[++t]=k;//方便输出
    }
}

        写kruskal的下一个部分:

for(int i=1;i<=m;i++)
{
	k=q.top();//取出堆顶元素
	q.pop();//排除 
	fun(k.s,k.e);//建立集合 
}

        按要求排序:

sort(a+1,a+t+1,cmp);

        但是cmp似乎还没有写呢!

        现要将将起点的编号从大到小排序;

        起点相同的的话,就将终点进行从小到大排序:

bool cmp(tree A,tree B)
{
	return A.s<B.s||A.s==B.s&&A.e<B.e;
}

        最后输出就行了:

for(int i=1;i<=t;i++)
		printf("%d  %d\n",a[i].s,a[i].e);

         kruskal算法就这样这样愉快的结束了

第四步

        最简单的主函数部分:

scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
	scanf("%d%d%d",&s.s,&s.e,&s.w);
	if(s.e<s.s)//起点比终点大 
		swap(s.s,s.e);//交换一下,无向图可以直接交换 
	q.push(s);//将这个点存入堆中 
}

         这个直接看我注释好了,

        最后调用kruskal算法:

kruskal();

        一道题就在这个愉快的结尾中结束了

AC代码:

#include<bits/stdc++.h>
#include<queue>
using namespace std;
int f[101],n,m,t;
struct tree{
	int s,e,w;
}a[101],s,k;
priority_queue<tree,vector<tree> >q;
bool operator<(tree A,tree B)
{
	return A.w>B.w; 
}
bool cmp(tree A,tree B)
{
	return A.s<B.s||A.s==B.s&&A.e<B.e; 
}
int find(int x)
{
	if(f[x]==x)
		return x; 
	return f[x]=find(f[x]);
}
void fun(int x,int y)
{
	x=find(x);
	y=find(y); 
	if(x!=y)
	{
		f[y]=x;
		a[++t]=k;
	}
}
void kruskal()
{
	for(int i=1;i<=n;i++)
		f[i]=i;
	for(int i=1;i<=m;i++)
	{
		k=q.top();
		q.pop();
		fun(k.s,k.e);
	}
	sort(a+1,a+t+1,cmp);
	for(int i=1;i<=t;i++)
		printf("%d  %d\n",a[i].s,a[i].e);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&s.s,&s.e,&s.w);
		if(s.e<s.s)
			swap(s.s,s.e);
		q.push(s); 
	}
	kruskal();
	return 0;
}

总结/结束语:

        其实kruskal算法就是一个并查集的进阶版,在思维上会有很大的启发,让我们再c++的道路上越走越远!

  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值