PAT上机考试模板

l         并查集:(union-find sets)



一种简单的用途广泛的集合. 并查集是若干个不相交集合,能够实现较快的合并和判断元素所在集合的操作,应用很多,如其求无向图的连通分量个数等。最完美的应用当属:实现Kruskar算法求最小生成树。



l         并查集的精髓(即它的三种操作,结合实现代码模板进行理解):



1、Make_Set(x) 把每一个元素初始化为一个集合



初始化后每一个元素的父亲节点是它本身,每一个元素的祖先节点也是它本身(也可以根据情况而变)。



2、Find_Set(x) 查找一个元素所在的集合



查找一个元素所在的集合,其精髓是找到这个元素所在集合的祖先!这个才是并查集判断和合并的最终依据。

判断两个元素是否属于同一集合,只要看他们所在集合的祖先是否相同即可。

合并两个集合,也是使一个集合的祖先成为另一个集合的祖先,具体见示意图



3、Union(x,y) 合并x,y所在的两个集合



合并两个不相交集合操作很简单:

利用Find_Set找到其中两个集合的祖先,将一个集合的祖先指向另一个集合的祖先。如图





l         并查集的优化



1、Find_Set(x)时 路径压缩

寻找祖先时我们一般采用递归查找,但是当元素很多亦或是整棵树变为一条链时,每次Find_Set(x)都是O(n)的复杂度,有没有办法减小这个复杂度呢?

答案是肯定的,这就是路径压缩,即当我们经过"递推"找到祖先节点后,"回溯"的时候顺便将它的子孙节点都直接指向祖先,这样以后再次Find_Set(x)时复杂度就变成O(1)了,如下图所示;可见,路径压缩方便了以后的查找。



2、Union(x,y)时 按秩合并

即合并的时候将元素少的集合合并到元素多的集合中,这样合并之后树的高度会相对较小。





 



 



 



//每一个集合都是一棵树,集合的元素则为树的节点,每棵树都有一个独一无二的标志,那、、//就是树的根节点



//一般的标志是自己本身的下标 或者 为-1



 



int father[MAX];  //father[x]表示x的父节点



int sign[MAX];    //sign[x] 用来记录查找根节点时,途中所路过的节点,压缩路径的时候用到



int rank[MAX]     //rank[x]  表示x节点所在树的深度



 



 



//初始化集合



 



void Make_Set(int x)



{



         father[x] = x;    //初始化一开始每个节点的父节点都为本身



         rank[x] = 0;      //初始化一开始每棵树的深度为



}



 



// 寻找x元素所在的集合也就是找子节点的根节点(树,若采用递归查找,回溯时压缩路径



 



int Find_Set(int x)



{



         if(father[x] != x)



         {



                  father[x] = Find_Set(father[x]); //这是一个递归的过程,回溯时压缩路径



         }



         return father[x];



}



 



        



 



 



void Union(int x,int y)    //合并两个不相交的集合,x,y分别为两个不同的集合



{



         x = Find_Set(x);



         y = Find_Set(y);



         if(x == y)  return ;    //若为同一集合,则直接返回



         if(rank[x] > rank[y])   //如果x树的深度比y树深,y树接到x树



         {



                  father[y] = x;



         }



         else if(rank[x] < rank[y])



         {



                  father[x] = y;



         }



         else if(rank[x] ==rank[y])  //若两树的深度一样



         {



                  father[x] = y;           //则x树接到y树



                  rank[y]++;              //此时y树的深度+1



         }



}





#include <iostream>

using namespace std;



#define Max 65535

int a[100][100];



/************************************************************************/

/*利用Prim算法求一个无向连通图的最小生成树,

从顶点iBegin开始构造。*/

/************************************************************************/

int Prim(int closedge[], int n, int iBegin)

{

    int i = 0;

    int j = 0;

    int iMin = 65535;

    int iSumCost = 0;

    int iCount = 0;

    int t = 0;

    //初始化辅助数组。

    for (i = 0; i < n; i++)

    {

        closedge[i] = 0;

    }

    closedge[iBegin] = 1;

    //找到构成最小生成树的n-1条边,并记录下它们的代价和。

    while (iCount < n-1)

    {

        iMin = 65535;

        for (i = 0; i < n; i++)

        {

            if (closedge[i] == 1)

            {

                for (j = 0; j < n; j++)

                {

                    if (i != j && a[i][j] < iMin && closedge[j] == 0)

                    {

                        iMin = a[i][j];

                        t = j; //记录下该顶点。

                    }

                }

            }

        }

        iSumCost += iMin;

        closedge[t] = 1; //将该顶点加入到已形成的集合中。

        iCount++;

    }

    return iSumCost;

}



int main()

{

    int i = 0;

    int j = 0;

    int k = 0;

    int iCost = 0;

    int n = 0;

    int iVexNum = 0;    

    int closedge[100];

    int iSumCost = 0;

    //初始化邻接矩阵。

    for (i = 0; i < n; i++)

    {

        for (j = 0; j < n; j++)

        {

            a[i][j] = Max;

        }

    }

    //cout<<"Please input the n:"<<endl;

    while(cin>>n && n)

    {

        iVexNum = n;

        for (k = 0; k < n*(n-1)/2; k++)

        {

            scanf( "%d%d%d" , &i , &j , &iCost );

            //cin>>i>>j>>iCost;

            a[i-1][j-1] = a[j-1][i-1] = iCost;

        }

        iSumCost = Prim(closedge, iVexNum, 0 );

        //cout<<"The minimum cost is:"<<endl;

        cout<<iSumCost<<endl;

    }

    /*system("pause");*/

    return 0;

}









//kruskal

#include <iostream>

#include <algorithm>

const int M=501;

using namespace std;

int n;

int ct=0;

int pre[M];

int graph[M][M];

struct edge

{

    int u,v; //首末结点

    int d;   //边的费用

}e[125001];

bool comp(const edge &a,const edge &b)

{

    return a.d<b.d;

}

int findanc(int x) //找祖先

{

    while(x!=pre[x])

        x=pre[x];

    return x;

}

int kruskal()

{

    int ans=0;

    for(int i=1;i<=n;i++) //结点个数

        pre[i]=i;

    sort(e,e+ct,comp);  //ct是边的个数

    for(int i=0;i<ct;i++)

    {

        int f1=findanc(e[i].u); 

        int f2=findanc(e[i].v);

        if(f1!=f2)   //如果首末两端的祖先不同,也就说明一条边在s中一条边在v-s中

        {

            ans+=e[i].d;

            pre[f1]=f2;    //把u的祖先设为v,这样就把那个结点加入s中了

        }

    }

    return ans;

}











//prim

#include <iostream>

#include <algorithm>

const int max_vertexes=501;

using namespace std;

int graph[max_vertexes][max_vertexes];

int n;

void prim(int vcount)//传入邻接矩阵大小

{

    int i,j,k,sum,father[500],min,max=0;

    int lowcost[max_vertexes],closeset[max_vertexes],used[max_vertexes];

    for (i=0;i<vcount;i++)

    {

        lowcost[i]=graph[0][i];//保存到达任何节点的最短路径

        closeset[i]=0;    //最近的节点

        used[i]=0;//保存使用过的顶点

        father[i]=-1;//父节点

    }

    used[0]=1;

    j=0;

    sum=0;

    for (i=1;i<vcount;i++)

    {

        min=100000;

        for (k=0;k<vcount;k++) 

        {

            if ((used[k]==0)&&(lowcost[k]!=0)&&(min>lowcost[k]))

            {//没被用过,通路,是最短通路



                j=k;//如果k没被使用过 且 最短 让j=k

                min=lowcost[j];

            }

        }

        if (lowcost[j]>max) max=lowcost[j];

        sum+=lowcost[j];

        father[j]=closeset[j];//连到最小生成树上

        used[j]=1;//第j个被用过

        //完成一个节点



        for (k=0;k<vcount;k++)//开始以j节点为开始,找最短路径

        {

            if (used[k]==0&&(graph[j][k]!=0))

            {//没用过,是通路

                if (lowcost[k]==0||(graph[j][k]<lowcost[k]))//k没被设置最小通路 或者 是连到下个节点的最短路径 

                {//lowcost是0的时候没考虑很可怕!!!!!

                    lowcost[k]=graph[j][k];//更新最近的节点

                    closeset[k]=j;//新更新的节点的父亲是j

                }

            }

        }

    }

    for (j=0;j<vcount;j++) cout<<father[j]<<endl;//父节点

    cout<<sum<<endl;//总长度

    cout<<max<<endl;//最长边

}

  

你可能感兴趣的:(pat)