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

来源:互联网 发布:cms 视频 编辑:程序博客网 时间:2024/05/18 15:56

*没听说过并查集的同学先移步看一下上篇博客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<stdio.h>#include<string.h>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(i<j) { for(;j>si;j--) if(V_SideMap[j].V_Value_By_ab<V_SideMap[si].V_Value_By_ab)  {  t=V_SideMap[j]; V_SideMap[j]=V_SideMap[si]; V_SideMap[si]=t; si=j; break;  } for(;i<si;i++) if(V_SideMap[i].V_Value_By_ab>V_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_Findint 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;i++){  scanf("%c", &N_cStPoint);  scanf("%d", &C_nListSum);  while(C_nListSum--)  {      getchar();      scanf("%c", &N_cEnPoint_temp);      scanf("%d", &V_nSide_temp);      Point_St_id=Get_PointId_by_PointName(N_cStPoint);      Point_En_id=Get_PointId_by_PointName(N_cEnPoint_temp);                  V_SideMap[C_nSideCount].V_nPoint_a=Point_St_id;      V_SideMap[C_nSideCount].V_nPoint_b=Point_En_id;      V_SideMap[C_nSideCount].V_Value_By_ab=V_nSide_temp;      C_nSideCount++;             }  getchar();}for(int i=1;i<=n;i++)P_nNextPoint[i]=i;Sort_By_Side(1,C_nSideCount-1);for(int i=1;i<C_nSideCount;i++)if(!Union(V_SideMap[i].V_nPoint_a,V_SideMap[i].V_nPoint_b))V_nMinAns+=V_SideMap[i].V_Value_By_ab;printf("%d\n",V_nMinAns);}return 0;}



注:此题n取值范围并不答,更适用于prim算法,只是为了说明并查集应用Kruskal算法。后续会有针对于本题的prim题解~









1 0
原创粉丝点击