这里我将用一道题的形式来详细的讲一下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(克鲁斯卡尔)算法是一种和并查集完美结合的最小生成树的算法。
Kruskal算法将一个连通块当作一个集合。Kruskal首先将所有的边按从大到小顺序排序(一般使用快排),并认为每一个点都是独立的,分属于n个独立的集合。
回到题:
我们要怎样才能让总工程量最小?
我的第一个思路就是图论,弗洛伊德试试,当然我说了要讲kruskal肯定不会讲弗洛伊德的,然后就写了一堆无用的东西……
当我正在准备测试样例的时候,我看到了这是最小生成树的题,其实我早就看到了,只是想冒个险(⊙﹏⊙)
最后我终于回归正道,其实早就该用Kruskal算法来做:
重点来了!!!
我们要建立一个结构体数组:
struct tree{
int s,e,w;
}a[101],s,k;
s表示的是起点,e表示的是终点,w表示的是花费(权值)
我们再建立一个小根堆:
priority_queue >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
最后输出就行了:
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
这个直接看我注释好了,
最后调用kruskal算法:
kruskal();
一道题就在这个愉快的结尾中结束了
#include
#include
using namespace std;
int f[101],n,m,t;
struct tree{
int s,e,w;
}a[101],s,k;
priority_queue >q;
bool operator<(tree A,tree B)
{
return A.w>B.w;
}
bool cmp(tree A,tree B)
{
return A.s
其实kruskal算法就是一个并查集的进阶版,在思维上会有很大的启发,让我们再c++的道路上越走越远!