【随便搞搞 2】Kruskal 的学习和使用

Tips:本题解是【随便搞搞 1】Prim算法的学习和使用 的姊妹篇,希望先阅读Prim算法。

预习及预备知识:

克鲁斯卡尔(Kruskal)算法是实现图的最小生成树最常用的算法。

大家知道,存储图的方法有2种:邻接矩阵表示法、邻接表表示法;

这里介绍的是介于这两种之间的一种方法:边接存储法(即直接用边来存储图)

但是最小生成树是什么呢?

标准定义如下:在边子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之和亦为最小。

听起来非常的带劲,我们就一起来探讨这一求最小生成树的算法!

Kruskal算法的三大特征:

●对于稠密图中求最小生成树优于Prim算法

●对于稀疏图中求最小生成树的时间复杂度O(n+m log m)

●标准Kruskal算法流程包含并查集的部分知识

算法思想:

先构造一个只含 n 个顶点、而边集为空的子图,把子图中各个顶点看成各棵树上的根结点,之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,即把两棵树合成一棵树,反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直到森林中只有一棵树,也即子图中含有 n-1 条边为止。

时间复杂度为为O(e^2), 使用并查集优化后复杂度为 O(eloge),与网中的边数有关,适用于求边稀疏的网的最小生成树。

克鲁斯卡尔算法从另一途径求网的最小生成树。假设连通网N=(V,{E}),则令最小生成树的初始状态为只有n个顶点而无边的非连通图T=(V,{∮}),图中每个顶点自成一个连通分量。在E中选择代价最小的边,若该边依附的顶点落在T中不同的连通分量上,则将此边加入到T中,否则舍去此边而选择下一条代价最小的边。依次类推,直至T中所有顶点都在同一连通分量上为止。

图例1

引入集合到集合的距离:对于两个集合AB;集合A中元素和B中元素各不相同

集合A中所有元素到集合B中所有元素的距离最小值定义为集合到集合的距离

贪心方法:每一次只要连接集合到集合距离最小的两个集合,反复n-1次得出的为最小生成树

对于上例,图为依照克鲁斯卡尔算法构造一棵最小生成树的过程。代价分别为1,2,3,4的四条边由于满足上述条件,则先后被加入到T中,代价为5的两条边(1,4)和(3,4)被舍去。因为它们依附的两顶点在同一连通分量上,它们若加入T中,则会使T中产生回路,而下一条代价(=5)最小的边(2,3)联结两个连通分量,则可加入T。因此,构造成一棵最小生成树。

图例2

a图中是输入的一个无向图;

b中连接集合到集合距离最小的两个集合1 3,{1,3}就是一棵最小生成树;

c图连接4 6,此时图中有两个元素大于等于2个的连通分支{1,3}{4,6}分别是最小生成树;

d图连接2 3,此时图中有三个元素大于等于2个的连通分支{1,3}{4,6}{2,5}分别是最小生成树;

e图连接两个连通分支{1,3}{4,6},图中有两个元素大于等于2个的连通分支{2,5}{1,3,4,6}分别是最小生成树;

f图连接两个连通分支{2,5}{1,3,4,6},图中有一个元素大于等于2个的连通分支{1,2,3,4,5,6}是最小生成树;

算法完成连通分支{1,2,3,4,5,6}就是关于全图a的最小生成树;

模板分析:


P3366 【模板】最小生成树

题目描述

如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出orz

输入输出格式

输入格式:

第一行包含两个整数N、M,表示该图共有N个结点和M条无向边。(N<=5000,M<=200000)

接下来M行每行包含三个整数Xi、Yi、Zi,表示有一条长度为Zi的无向边连接结点Xi、Yi

输出格式:

输出包含一个数,即最小生成树的各边的长度之和;如果该图不连通则输出orz

输入输出样例

输入样例#1:

4 5

1 2 2

1 3 2

1 4 3

2 3 4

3 4 3

输出样例#1:

7

说明

时空限制:1000ms,128M

数据规模:

对于20%的数据:N<=5,M<=20

对于40%的数据:N<=50,M<=2500

对于70%的数据:N<=500,M<=10000

对于100%的数据:N<=5000,M<=200000

样例解释:

所以最小生成树的总边权为2+2+3=7


【分析】

对于样例,我们把1 2 3 4四点抽象为含有一个元素的子集{1}{2}{3}{4}

首先看到子集{1}{3}之间距离最小,且遍历越靠后,合并形成一个连通分支{1,3}{2}{4}

看到子集{2}{1,3}之间距离最小,合并形成一个连通分支{1,2,3}{4}
看到子集{1,2,3}{4}之间距离最小,合并形成一个连通分支{1,2,3,4}

看到连通分支{1,2,3,4}是全局的最小生成树,所以路径和为2+2+3=7

【实现】

type rec=record
 v1,v2,len:longint
end;
var n,m,i:longint;
    a:array[0..200000]of rec;
    pre:array[0..200000]of longint;//并查集记录某点的父亲结点
procedure qsort(l,r:longint);//按照各边长度排序,复杂度O(E log E)
var i,j,mid:longint;
    t:rec;
begin
 i:=l; j:=r; mid:=a[(l+r)div 2].len;
 repeat
  while a[i].len<mid do inc(i);
  while a[j].len>mid do dec(j);
  if i<=j then begin
   t:=a[i]; a[i]:=a[j]; a[j]:=t;
   inc(i); dec(j);
  end;
 until i>=j;
 if j>l then qsort(l,j);
 if i<r then qsort(i,r);
end;
function getfather(x:longint):longint;//并查集求父亲结点,复杂度为O(log N)~O(N)
begin
 if pre[x]=x then exit(x)
 else getfather:=getfather(pre[x]);
end;
procedure kruskal;
var tot,i,j,p,q:longint;
begin
 for i:=1 to n do pre[i]:=i;//并查集的初始化,各点的父亲结点是自己
 p:=n-1; q:=1; tot:=0;//p表示当前剩下还有几条边没连(最小生成树的边数为点数-1);//q表示当前遍历到第几条边//tot表示最小生成树的边权之和
 qsort(1,m);//对边的长度快排
 while (p>0)and(q<=n) do begin//如果没找完或者边数没有遍历完
  i:=getfather(a[q].v1);//i表示当前遍历到该边的一个端点的父亲
  j:=getfather(a[q].v2);//j表示当前遍历到改边的另外一个端点的父亲
  if i<>j then begin //如果这两个点不属于一个父亲,即这两个点所在的连通分支不是同一个,或这两个连通分支不相连通
   inc(tot,a[q].len);//找到一条最短边,即这是这两个连通分支连接的不唯一的但是最短的连接法(等价于其他连接法)
   pre[i]:=pre[j];//把这两点的父亲结点并在一起,即把这两个点所在的两个连通分支合并
   dec(p);//找到一条符合条件的边
  end;  inc(q);//下一条边
 end; if q>n then writeln(‘orz‘)//如果边全部遍历完但是还是没找到最小生成树的判定为不能生成最小生成树 else writeln(tot);//否则就是能生成最小生成树,打印,各边权值之和
end;
begin
 readln(n,m);//n个点,m条边
 for i:=1 to m do readln(a[i].v1,a[i].v2,a[i].len);//读入第i条边的端点1,端点2,边权len
 kruskal;
end.

时间复杂度的推导:

初始化 所有点各自为一个森林 这一步是O(n)的

并且把边集进行从小到大排序,这一步如果使用快速排序或者堆排序是O(mlogm)

然后在这一片森林中添加边,我们知道n个点构成的树是有n-1条边,因此需要执行n-1次以下操作

从已经排序的边序列中,挑选长度最短的,且两端不在同一棵树中的一条边,判断是否是同一棵树是利用并查集进行查询,挑出这一条边之后,把两个端点代表的树合并为一棵,即并查集的合并,这也是O(1)的

注意到在选取边的过程中,只要挑选其中的n-1条,因此挑选边的n-1次挑选边的复杂度之和是O(m)的(可以理解为看最后一条连接的边在序列的第几条,最坏情况就是最后一条才能使n个点连通,因此最坏复杂度是O(m)

因此总复杂度为O(n+mlogm)

 应用:

有了这一个神奇的算法我们能干什么呢?

村庄修路,网络铺设,关于最小生成树的问题,应用比较广,学科范围广,这里给一个模板题:

Networking

Time Limit: 1000MS   Memory Limit: 10000K
Total Submissions: 6623   Accepted: 3608

Description

You are assigned to design network connections between certain points in a wide area. You are given a set of points in the area, and a set of possible routes for the cables that may connect pairs of points. For each possible route between two points, you are given the length of the cable that is needed to connect the points over that route. Note that there may exist many possible routes between two given points. It is assumed that the given possible routes connect (directly or indirectly) each two points in the area. 
Your task is to design the network for the area, so that there is a connection (direct or indirect) between every two points (i.e., all the points are interconnected, but not necessarily by a direct cable), and that the total length of the used cable is minimal.

Input

The input file consists of a number of data sets. Each data set defines one required network. The first line of the set contains two integers: the first defines the number P of the given points, and the second the number R of given routes between the points. The following R lines define the given routes between the points, each giving three integer numbers: the first two numbers identify the points, and the third gives the length of the route. The numbers are separated with white spaces. A data set giving only one number P=0 denotes the end of the input. The data sets are separated with an empty line. 
The maximal number of points is 50. The maximal length of a given route is 100. The number of possible routes is unlimited. The nodes are identified with integers between 1 and P (inclusive). The routes between two points i and j may be given as i j or as j i.

Output

For each data set, print one number on a separate line that gives the total length of the cable used for the entire designed network.

Sample Input

1 0

2 3
1 2 37
2 1 17
1 2 68

3 7
1 2 19
2 3 11
3 1 7
1 3 5
2 3 89
3 1 91
1 2 32

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

0

Sample Output

0
17
16
26
参见上面的程序,prim注意有重边的处理,但是Kruskal不用担心重边的问题
时间: 2024-08-22 18:30:22

【随便搞搞 2】Kruskal 的学习和使用的相关文章

【随便搞搞 1】 prim算法的学习和使用

最小生成树是图论中非常有用的算法. 就不知怎么的就学会的最小生成树~~ 但是最小生成树是什么呢? 标准定义如下:在边子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之和亦为最小. 听起来非常的带劲,我们就一起来探讨这一求最小生成树的算法! prim 的四大特征: ●最小生成树算法中prim算法是耗时最长的 ●最小生成树算法中prim算法是适用于求稠密图的 ●最小生成树算法中prime算法最简单易懂 ●请不要多打一个e否则就是prime质数了 例子: P3366 [模板]最小生成树

hdu 5249区间第k大(学习了下树状数组的搞法)

KPI Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 205    Accepted Submission(s): 70 Problem Description 你工作以后, KPI 就是你的全部了. 我开发了一个服务,取得了很大的知名度.数十亿的请求被推到一个大管道后同时服务从管头拉取请求.让我们来定义每个请求都有一个重要值.我的K

【学习笔记】dsu on tree

我也不知道为啥这要起这名,完完全全没看到并查集的影子啊-- 实际上原理就是一个树上的启发式合并. 特点是可以在$O(nlogn)$的时间复杂度内完成对无修改的子树的统计,复杂度优于莫队算法. 局限性也很明显:1.不能支持修改  2.只能支持子树统计,不能链上统计.(链上统计你不能直接树剖吗?) 那么它是怎么实现的呢?首先有一个例子:树上每个节点都有一个颜色(那么一定是蓝色), 求每个节点的子树上有多少颜色为k的节点.(每个节点的k不一定相同) $O(n^2)$的算法非常好想,以每个点为起点dfs

诶西,JavaScript学习记录。。。。。。

由于大学课程缘故,老师巨爱叫人问问题,还记分呢,随便记录一下Js的学习情况,以后复习什么的也比较方便吧...... 开始咯,就按照C语言学习那样的方法来吧! ==================================割割割================================== 1.数据类型(这里只是大概提一下) 1 /* 2 我认为Js里没有明显的数据类型,仅有 字符串.数字.布尔.数组.对象.Null.Undefined 3 */ 4 5 var temp = 'leg

我为何要拼命地学习

一.浮生若梦 2012年9月,我迈进高中的大门. 彼时,我怀揣着初中对高中的规划:高一好好玩,高二开始学习.想想那时,真的天真的像个高一的孩子.于是,我付诸实践,晚上看看小说,白天上课也不那么专注.虽然我的成绩还算理想,但我的学习态度以及对知识的钻研岌岌可危.这个从我厌恶化学而不采取行动学会它反而直接放弃它可以看出我懒散的本性.我自始至终承认懒散是我人生路上的始终存在的绊脚石,只是我不敢向旁人道明.高二,在繁重的课业负担下,我依然只是按部就班地完成学校的任务,除了学习到书本上的知识,我感觉我仍然

平衡树学习小记

总起 修炼了2天,终于差不多完成基础了,数据结构都是很灵活的,不仅是应用,而且写代码也是有很多值得思考的地方.而在平衡树中,旋转是核心的核心. 先总结一下吧. 先说明一些概念 键值,所谓的key,我一般用val表示,就是当前点存的值. ind(ex),引索,就是用平衡树要维护的东西,可能还用wei(ght)来表示.相当于普通序列中的下标. 虚拟节点:第n+1个点,放在所有点之前,让平衡树有头:第n+2个点,放在最后,让平衡树有尾.这两个点没有实际意义,用作连接. siz(e),以当前点为根的子树

推荐一个程序员系统学习网址

今天给大家分享一个十分有用的学习网站!个人感觉对于入门者来说是十分适合的!并且内容权威!非常适合在校学生或者想要巩固基础的朋友来学习使用! 这里部分学习内容,可以看到内容非常的系统,是按照学习路线来的! 我们随便看一下前端开发学习路线: 可以看到前端开发学习路线分为了 5 大学习阶段,一共有 15 门免费课程.而且在我们学习完课程后还有对应的自测考试!重要的是全程免费 好了,具体的内容我就不再重复了,大家进入网址自己看就行了. 这里是用的前端举的例子,还有很多其他的学习路线也是一样的!非常的系统

POJ 2528 Mayor&amp;#39;s posters 离散化+线段树

题目大意:给出一些海报和贴在墙上的区间.问这些海报依照顺序贴完之后,最后能后看到多少种海报. 思路:区间的范围太大,然而最多仅仅会有10000张海报,所以要离散化. 之后用线段树随便搞搞就能过. 关键是离散化的方法,这个题我时隔半年才A掉,之前一直就TTT,我还以为是线段树写挂了. 当我觉得我自己的水平这样的水线段树已经基本写不挂的时候又写了这个题,竟然还是T. 后来我对照别人的代码,才发现是我的离散化写渣了. 以下附AC代码(79ms),这个离散化写的比較优雅.时间也非常快,以后就这么写了.

【BZOJ】3065: 带插入区间K小值

题意:带插入.修改的区间k小值在线查询.(原序列n<=35000, 询问<=175000) #include <bits/stdc++.h> using namespace std; const int nTr=1000005, nSg=15000005, alphaA=4, alphaB=5; int STop; struct Seg *Snull; struct Seg { Seg *l, *r; int s, cnt; }Sg[nSg], *iSg=Sg, *bin[nSg]