天天看点

数据结构-并查集

适合需要反复用到查询某个元素归属于哪个集合的运算。

初始化时每个元素自成为单元素集合,parent均为-1。此后parent[x]>0时表示元素x的父在数组中的下标;parent[x]<0时表示以元素x为根的集合的元素个数

Find操作可以查询元素归属哪个集合,搜索并返回包含元素x的根即可,有两种方法:

(1)非递归方法:while(parent[x]>=0) x=parent[x];

(2)递归方法:x为根时直接返回x,否则继续Find(parent[x])递归找x的父的根

Union操作进行集合合并,默认把元素Root2嫁接到Root1上:parent[Root1]+=parent[Root2]; parent[Root2]=Root1;

执行一次Union操作需要O(1),而n-1次Union操作可以在O(n)内完成。

性能不好,可能会产生退化了的树。此时Find操作需要从被搜索元素出发,沿父指针链走到根,完成Find(i)的时间复杂度为O(i),而完成n次搜索需要的总时间为O(n^2)

可以使用加权规则得到改进Union操作,使用结点个数探查法求两个UFSets类集合的并,结点个数少的集合嫁接到结点个数多的集合上。执行一次WeightedUnion操作的时间比执行一次Union的时间稍多,但仍在常量界限O(1)范围内。

折叠规则压缩路径的算法CollapsingFind:如果元素j是从元素i到根的路径上的一个结点,并且元素j的父结点不是根,则把元素j的父结点置为根

const int DefaultSize=10;
class UFSets{
public:
    UFSets(int sz=DefaultSize);
    ~UfSets(){delete []parrent;}
    UFSets& operator=(UFSets& R);
    void Union(int Root1,int Root2);
    int Find(int x);   
    void WeightedUnion(int Root1,int Root2);
    int CollapsingFind(int i);
private:
    int *parent;
    int size;
}

UFSets::UFSets(int sz){
    size=sz;
    parent=new int[size];
    for(int i=0;i<size;i++) parent[i]=-1;  //初始化时每个元素自成为单元素集合
}

int UFSets::Find(int x){
    //非递归方法,搜索并返回包含元素x的根
    while(parent[x]>=0) x=parent[x];
    return x;
    
    //  一种递归方法,x为根时直接返回x,否则递归找x的父的根
    //  if(parent[x]<0) return x;
    //  else return Find(parent[x]);
}

void UFSets::Union(int Root1,int Root2){
    parent[Root1]+=parent[Root2];
    parent[Root2]=Root1;
}

void UFSets::WeightedUnion(int Root1,int Root2){
    int r1=Find(Root1),r2=Find(Root2),temp;
    if(r1!=r2){
        temp=parent[r1]+parent[r2];
        if(parent[r2]<parent[r1]){  //r2结点个数更多
            parent[r1]=r2;
            parent[r2]=temp;
        }
        else{
            parent[r2]=r1;
            parent[r1]=temp;
        }
    }
}

int UFSets::CollapsingFind(int i){
    for(int j=i;parent[j]>=0;j=parent[j]);  //在包含i的树中搜索根j
    while(i!=j){         //将从j到根的路径上所有结点都变成根的子女
        int temp=parent[i];
        parent[i]=j;
        i=temp;
    }
    return j;
}