*没听说过并查集的同学先移步看一下上篇博客http://blog.csdn.net/sm9sun/article/details/53256232
好,首先说一下并查集的标准定义:
概述:
在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
结构:
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
集就是让每个元素构成一个单元素的集合,也就是按一定顺序将属于同一组的元素所在的集合合并。
操作:
初始化
把每个点所在集合初始化为其自身。
通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为O(N)。
查找
查找元素所在的集合,即根节点。
合并
将两个元素所在的集合合并为一个集合。
通常来说,合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现。
回到上篇博客的题目,畅通工程的边没有权值,所以相对来说,比较简单,如果有权值或者单向图,并查集可以做吗?显然也是可以的。
因为并查集是树形结构,本身其边就是带有指向性。
我们说并查集的主要用途在于连接、查找、合并操作,那么其应用最广的领域即为图的最小生成树问题——
图的最小生成树:如果连通图G的一个子图是一棵包含G的所有顶点的树,则该子图称为G的生成树。生成树是连通图的包含图中的所有顶点的极小连通子图。(图的生成树不惟一。从不同的顶点出发进行遍历,可以得到不同的生成树)而权值最小的树就是最小生成树。
我们比较熟悉的最小生成树算法是prim算法,其思路是把每两个点的连接状态全部存储下来,即一个二维的邻接矩阵。然后通过贪心的方法进行连接
但如果其点非常多,NxN的邻接矩阵可能会扛不住,而未必每两个点的连接情况都有价值。比如说大部分两个点都没有连接关系。
那么这种情况,我们就适合用于以边计算的Kruskal算法。
Kruskal算法就是在剩下的所有未选取的边中,找最小边,如果和已选取的边构成连接,则放弃,选取次小边。那么连接操作、判断是否已经构成连接,就是运用的
并查集的算法思想。我们看一道例题:
题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=1301
题目描述:
给定村庄数n,用字母表的前n个字母表示,接下来n-1行每行一个村庄字母和与其连接的村庄数以及各村庄的字母和距离。求最小生成树。
解题思路:
用Kruskal算法解决此题,先将所有的边进行排序,依次选取最小的边进行连接,如果该边的2个点已经连接,则遍历次小的边
首先我们构造边的结构体:
struct Side
{
int V_nPoint_a; //a点
int V_nPoint_b; //b点
int V_Value_By_ab;//连接ab的边权值
}V_SideMap[5050];
并查集:
int find(int x) //查找根
{
if(P_nNextPoint[x]!=x)
P_nNextPoint[x]=find(P_nNextPoint[x]);
return P_nNextPoint[x];
}
int Union(int a,int b) //合并
{
int x,y;
x=find(a);
y=find(b);
if(x==y) return 1;
else
{
P_nNextPoint[y]=x;
return 0;
}
}
#include
#include
int Get_PointId_by_PointName(char c)
{
return int(c-64);
}
struct Side
{
int V_nPoint_a;
int V_nPoint_b;
int V_Value_By_ab;
}V_SideMap[5050];
int C_nSideCount;
void Sort_By_Side(int l,int r)
{
if(l>=r)
return;
Side t;
int j=r;
int i=l;
int si=(l+r)/2;
while(isi;j--)
if(V_SideMap[j].V_Value_By_abV_SideMap[si].V_Value_By_ab)
{
t=V_SideMap[i];
V_SideMap[i]=V_SideMap[si];
V_SideMap[si]=t;
si=i;
break;
}
}
Sort_By_Side(l,i-1);
Sort_By_Side(j+1,r);
}
//Union_Find
int P_nNextPoint[30];
int find(int x)
{
if(P_nNextPoint[x]!=x)
P_nNextPoint[x]=find(P_nNextPoint[x]);
return P_nNextPoint[x];
}
int Union(int a,int b)
{
int x,y;
x=find(a);
y=find(b);
if(x==y) return 1;
else
{
P_nNextPoint[y]=x;
return 0;
}
}
int main()
{
char N_cStPoint,N_cEnPoint_temp;
int C_nListSum;
int V_nSide_temp;
int Point_St_id,Point_En_id;
int n;
int V_nMinAns;
while(scanf("%d",&n)!=EOF&&n)
{
getchar();
C_nSideCount=1;
V_nMinAns=0;
for(int i=1;i
注:此题n取值范围并不答,更适用于prim算法,只是为了说明并查集应用Kruskal算法。后续会有针对于本题的prim题解~