经典算法系-并查集(Union-Find Sets)

1. 并查集(Union-Find Sets)

 一种树型数据结构,用于处理不相交集合(Disjoint Sets)的合并以及查询;一开始让所有元素独立成树,也就是只有根节点的树;然后根据需要将关联的元素(树)进行合并;合并的方式仅仅是将一棵树最原始的节点的父亲索引指向另一棵树;

 优化:加入一个rank数组存储节点深度的下界(从当前节点到其最远子节点的距离),从而可以启发式的对树进行合并,从而减少树的深度,防止树的退化;使得包含较少节点的树根指向包含较多节点的树根,具体指代为树的高度;另一个优化就是路径压缩,尽可能将子节点都直接连接到根节点之后;

 并查集的空间复杂度为O(N),构建一个集合的时间复杂度为O(N);压缩后的查找复杂度是一个很小的常数;应用:Kruskal算法求最小生成树中判断新加入的边是否在同一棵树内部;两个节点的最近公共祖先(Least Common Ancestors);

初始化father:各个节点独立成树,并且其father[i]=i,也就是其父节点就是其自身;
father[i]
0 1 2 3 4 5 6 7 8 9

0 1 2 3 4 5 6 7 8 9

初始化rank:各个节点为根节点,所以高度都为1;
rank[i]
0 1 2 3 4 5 6 7 8 9

1 1 1 1 1 1 1 1 1 1

合并2和6:由于rank[2]=rank[6],所以将2的父亲索引指向6,这样2和6就在同一棵树;并需将6的rank值增加1;
father[i]
0 1 2 3 4 5 6 7 8 9
0 1 6 3 4 5 6 7 8 9
rank[i]
0 1 2 3 4 5 6 7 8 9

1 1 1 1 1 1 2 1 1 1

下述为代码实现:
int *father;
int *rank;

/**
 * 并查集的初始化:
 * 数组father中的元素在最开始ide时候都是独立的树,也就是只有根节点
 * 的树,数组father的下标i表示节点,而father[i]的值表示i节点的父亲
 * 节点;rank[i]=1表示一开始所有树节点的高度都为1
 * */
void init(int cap) {
        father=new int[cap];
        rank=new int[cap];
        /**
         * 时间复杂度为O(N)
         * */
        for(int i=0;i<10;i++) {
                father[i]=i;
                rank[i]=1;
        }
}

void clean() {
        delete [] father;
        delete [] rank;
}
/**
 * 查找元素所在的集合并进行路劲压缩:
 * 由于需要频繁使用GetFather()函数,并且其时间复杂度受树结构影响;
 * 当元素较多的时候,集合退化成链表,则GetFather()需要O(N),所以
 * 需要对其进行优化,每次调用GetFather()的时候都将输入元素压缩成
 * 最原始父亲节点的直接子节点
 * */
int GetFather(int son) {
        if(father[son]==son)
                return son;
        else {
                father[son]=GetFather(father[son]);
                return father[son];
        }
}

/**
 * 合并两个不相交的集合:
 * 输入元素x和y来自两个不相交的集合,找到其最原始的父亲节点
 * 并将一个原始父亲节点设置为另一个原始父亲节点的父亲节点
 * */
void Union1(int x, int y) {
        /**
         * GetFather()为递归寻找输入节点的最原始的父亲节点
         * */
        int fx=GetFather(x);
        int fy=GetFather(y);
        /**
         * 判断x和y是否来自同一棵树,如果不是才进行赋值;其实可以
         * 不同进行判断(省去if语句)
         * 注意最原始父亲节点的father[i]=i;
         * */
        if(fx!=fy)
                father[fx]=fy;
}

/**
 * 利用rank加权数组启发式进行合并
 * */
void Union2(int x, int y) {
        int fx=GetFather(x);
        int fy=GetFather(y);

        if(fx==fy) return;
        /**
         * rank[fx]较大,说明其越靠近根节点,则将
         * fy连接到其后面可以压缩路径
         * */
        if(rank[fx]>rank[fy])
                father[fy]=fx;
        else {
                if(rank[fx]==rank[fy])
                        rank[fy]++;
                father[fx]=fy;
        }
}

/**
 * 判断两个元素是否属于同一个集合:
 * 利用GetFather()函数判断其最原始父亲节点是否相同
 * */
bool IsSameSet(int x, int y) {
        return GetFather(x)==GetFather(y);
}
转载自 https://www.douban.com/note/210478218/?type=like

你可能感兴趣的:(算法)