并查集(Union-find Set)及java实现

并查集

并查集处理集合之间的关系,即 'union'合并 和 'find'查找。
在这种数据类型中,N个不同元素被分成若干个组,每组是一个集合,这种集合叫做分离集合。
并查集支持查找一个元素所属的集合和两个元素分别所属的集合的合并。

并查集支持的操作
MAKE(X): 建立一个仅有成员X的新集合。
UNION(X,Y):将包含X和Y的动态集合合并为一个新集合S,此后该二元素处于同一集合。
FIND(X):返回一个包含X的集合。
注意:并查集只能进行合并操作,不能进行分割操作。

并查集实现原理
并查集是使用树结构实现的:
1.初始化:准备N个节点来表示N个元素,最开始没有边。
2.合并:从一个组的根向另一个组的根连边,这样两棵树就变为了一棵树,也就把两个组合并为一个组了。
3.查询:查询两个节点是否在同一个组,只需要查询他们是否具有相同的根。

注意:
(1)为避免树的退化,对于每棵树,记录其高度rank。
(2)如果合并时两棵树高度不同,那么从rank小的向rank大的连边。
(3)为了简化后续查询,在查询某个节点时,一旦向上走到了一次根节点,就把这个节点到父亲的边改为直接连向根。不仅指查询的节点,也包括查询过程中经过的节点。使用这种简化的方法时,即使树的高度发生了改变,我们也不修改rank值。

并查集的复杂度
对N个元素并查集进行的一次操作的复杂度是O(α(N)),α(N)是阿克曼函数的反函数。这个复杂度是比O(LogN)还要快的。

java实现

import java.util.HashMap;
import java.util.List;
import java.util.Arrays;

public class UnionFind<E> {
    private HashMap<E,E> parent = null; // 一棵树表示一个集合
    private HashMap<E,Integer> rank = null;  // 记录树高度信息,避免树退化
    private int count = 0; // 集合数

    public UnionFind(List<E> list) {
        parent = new HashMap<>();
        rank = new HashMap<>();
        count = list.size();
        for ( E e : list ) {
            parent.put(e, e);
            rank.put(e, 0);
        }
    }

    public HashMap<E, E> parent() {
        return parent;
    }

    public HashMap<E, Integer> rank() {
        return rank;
    }

    public int numSubsets() {
        return count;
    }

    // 查找元素
    public E find(E e) {
        E pe = parent.get(e);
        while ( ! pe.equals(e) ) {
            parent.put(e, parent.get(pe)); // 将该节点到父亲的边更新为直接连向根,方便后续查询
            e = parent.get(e);
            pe = parent.get(e);
        }
        return e;
    }

  // 合并两个节点所在的集合
    public void union(E e1, E e2) {
        E root1 = find(e1), root2 = find(e2);
        // 若两个节点在同一个节点(根相同)则不合并
        if ( root1.equals(root2) ) {
            return;
        }
        // 按照树高rank,将低的连到高的, 树高不变
        if ( rank.get(root1) < rank.get(root2) ) {
            parent.put(root1, root2);
        }
        else if ( rank.get(root1) > rank.get(root2) ) {
            parent.put(root2, root1);
        }
        // 若两棵树高度相同,连后高度+1
        else {
            parent.put(root2, root1);
            rank.put(root1, 1 + rank.get(root1));
        }
        count--; // 连后树数量-1
    }

    public static void main(String[] args) {
        System.out.println("初始化: ");
        String[] labels = {"a", "b", "c", "d", "e", "f", "g", "h"};
        UnionFind<String> uf = new UnionFind<>(Arrays.asList(labels));
        System.out.println("uf.parent() = " + uf.parent());
        System.out.println("uf.rank() = " + uf.rank());
        System.out.println("count = " + uf.numSubsets());

        System.out.println("join b & e, c & d 之后: ");
        uf.union("b", "e");
        uf.union("c", "d");
        System.out.println("uf.parent() = " + uf.parent());
        System.out.println("uf.rank() = " + uf.rank());
        System.out.println("count = " + uf.numSubsets());

        System.out.println("join e & h, a & g, h & c 之后: ");
        uf.union("e", "h");
        uf.union("a", "g");
        uf.union("h", "c");
        System.out.println("uf.parent() = " + uf.parent());
        System.out.println("uf.rank() = " + uf.rank());
        System.out.println("count = " + uf.numSubsets());

        System.out.println("join f & g 之后:");
        uf.union("f", "g");
        System.out.println("uf.parent() = " + uf.parent());
        System.out.println("uf.rank() = " + uf.rank());
        System.out.println("count = " + uf.numSubsets());
    }
}

输出结果为:

初始化: 
uf.parent() = {a=a, b=b, c=c, d=d, e=e, f=f, g=g, h=h}
uf.rank() = {a=0, b=0, c=0, d=0, e=0, f=0, g=0, h=0}
count = 8
join b & e, c & d 之后: 
uf.parent() = {a=a, b=b, c=c, d=c, e=b, f=f, g=g, h=h}
uf.rank() = {a=0, b=1, c=1, d=0, e=0, f=0, g=0, h=0}
count = 6
join e & h, a & g, h & c 之后: 
uf.parent() = {a=a, b=b, c=b, d=c, e=b, f=f, g=a, h=b}
uf.rank() = {a=1, b=2, c=1, d=0, e=0, f=0, g=0, h=0}
count = 3
join f & g 之后:
uf.parent() = {a=a, b=b, c=b, d=c, e=b, f=a, g=a, h=b}
uf.rank() = {a=1, b=2, c=1, d=0, e=0, f=0, g=0, h=0}
count = 2
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,783评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,396评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,834评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,036评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,035评论 5 362
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,242评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,727评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,376评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,508评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,415评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,463评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,140评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,734评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,809评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,028评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,521评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,119评论 2 341