并查集与Kruskal算法(最小生成树)

基本并查集建立

并查集是一种合并不相交集合的数据结构 ,常用在寻找图中是否存在环路
建立并查集就是在建立一个尽量平衡的树。边建立边检测是否存在环路

最重要的两点:

  1. 找到根节点 find_root(x)
  2. 合并两棵树 union(xTree,yTree)
#include <stdc++.h>
using namespace std;
#define VERUTICES 6     //结点数
 

int find_root(int x,int parent[]){
    int x_root=x;
    while(parent[x_root]!=-1){
        x_root=parent[x_root];
    }
    return x_root;
}


//return : 1   --success
//         0   --fail
int union_vertices(int x,int y,int parent[]){
    int x_root=find_root(x,parent);
    int y_root=find_root(y,parent);
    if(x_root == y_root){
        return 0;
    }else{
        parent[x_root]=y_root;
        return 1;
    }
}


int main(){
    int parent[VERUTICES]={0};
    //二维数组存储边
    int edges[5][2]={
        {0,1},{1,2},{1,3},{3,4},{2,5}
    };
    memset(parent,-1,sizeof(parent));   //初始化父结点数组

    //遍历6条边
    for(int i=0;i<5;i++){
        int x=edges[i][0];
        int y=edges[i][1];
        if(union_vertices(x,y,parent)==0){
            cout<<"存在环路"<<endl;
            return 0;
        }
    }
    cout<<"无环路"<<endl; 
    return 0;
}

压缩路径

再进一步压缩路径,使其更加平衡:

我们需要在以上代码基础上添加:

1.rank 数组 标记根节点所在的树深高度,并维护(注:rank[]只有在两个集合树高度相等时拼接一起才需要+1)

2.下列代码 实际每个节点的rank[]值代表的是当前节点在树的高度-1

#include <stdc++.h>
using namespace std;
#define VERUTICES 6     //结点数
 

int find_root(int x,int parent[]){
    int x_root=x;
    while(parent[x_root]!=-1){
        x_root=parent[x_root];
    }
    return x_root;
}


//return : 1   --success
//         0   --fail
int union_vertices(int x,int y,int parent[],int rank[]){
    int x_root=find_root(x,parent);
    int y_root=find_root(y,parent);
    if(x_root == y_root){
        return 0;
    }else{
        if(rank[x_root]>rank[y_root]){
            parent[y_root]=x_root;
        }else if(rank[x_root]<rank[y_root]){
            parent[x_root]=y_root;
        }else{
            parent[x_root]=y_root;
            rank[y_root]++;
        }
        return 1;
    }
}


int main(){
    int parent[VERUTICES]={0};
    int rank[VERUTICES]={0};
   
    //二维数组存储边
    int edges[5][2]={
        {1,2},{3,4},{2,5},{1,3},{0,1}
    };
    memset(parent,-1,sizeof(parent));   //初始化父结点数组

    //遍历5条边
    for(int i=0;i<5;i++){
        int x=edges[i][0];
        int y=edges[i][1];
        if(union_vertices(x,y,parent,rank)==0){
            cout<<"存在环路"<<endl;
            return 0;
        }
    }
    cout<<"无环路"<<endl; 

    for(int i=0;i<5;i++){
        cout<<i<<": "<<rank[i]<<endl;
    }
    return 0;
}

ps:额这个案例边数组比较特殊,大家可以改一改顺序,会得出不同rank值


与Kruskal算法(最小生成树)之间的关系

Kruskal算法:针对有 n 个结点的带权无向图,按边的权值从大到小排,,每次从其中取出一条边,两个结点(注意是两个不同连通分量的结点,也就是不能成环)。最后生成 n-1 条边的 最小生成树(边最少包含所有顶点)。

在克鲁斯卡尔算法中,我们需要将两个不同连通分量合并,不能有环。

在并查集中,我们合并的是两个不同的树型区域,不能有环。

可以发现,克鲁斯卡尔最重要的合并操作 在code 实现上正是由并查集这一数据结构完成的。

譬如图一是所有结点权值,将所有结点独立,在几次选边合并操作后变为图二,此时合并图二两个连通分量可以用并查集。

posted @ 2021-12-02 21:34  秋月桐  阅读(246)  评论(0编辑  收藏  举报