并查集——最小连接路径和Kruskal(hdu1301)

*没听说过并查集的同学先移步看一下上篇博客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题解~









你可能感兴趣的:(ACM,nine_sun算法专栏)