并查集

并查集 (Disjoint Set Union) 是一种树形的数据结构,用于处理不交集的合并 (union) 及查询 (find) 问题。

实现

  1. 一个parent数组用来储存每个节点 (node) 的最终父节点 (root)
  2. 一个find方法用来找寻当前节点 (node) 的最终父节点 (root)
  3. 一个union方法用来合并两个符合关系的节点
  4. main方法对于需要合并的节点进行判断,并使用合并后的集合
//find the root parent of index i, it represent the whole cluster
public int find( int[] parent, int i ) {
    if(parent[i] == i) {
        return parent[i];
    }
    return find( parent, parent[i] );
}

//merge two node to the same parent because they are correlated
public void union ( int[] parent, int i, int j) {
    int ip = find ( parent, i );
    int jp = find ( parent, j );
    if ( ip != jp ) {
        parent[ jp ] = ip; 
    }
}

public int DSU(int[][] M) {
    if ( M.length == 0 ) {
        return 0;
    }
    //initialize the parent array
    int[] parent = new int [ M.length ];
    for (int i = 0; i < M.length; i++) {
        parent[ i ] = i;
    }
    for (int i = 0; i < M.length; i++) {   
        for (int j = 0; j < M.length; j++) {
            //if satisfied, merge them to one union
            if ( i != j && M[i][j] == 1) {
                union( parent, i, j );
           }
        }
    }
    int count = 0;
    //use the union result
    for (int i = 0; i < parent.length; i++) {
        if ( parent[i] == i ) {
            count++;       
        }
        return count;
}

写在一个function内

public int[] findRedundantConnection ( int[][] edges ) {
    //only one redundant edge, so number of nodes = edges.length
    int[] parent = new int [ edges.length ];
    for(int i = 0; i < edges.length; i++) {
        parent[ i ] = i;
    }
    for(int i = 0; i < edges.length; i++) {
        // because the node start at 1, parent[i] represent the parent of node i+1
        // minus 1 for check and modification, plus 1 when return results
        int start = edges[i][0]-1;
        int end = edges[i][1]-1;
        // ---------------- equivalent to find() function --------------------
        // recursively get the root of the union of start
        int sroot = start;
        while ( sroot != parent[sroot] ) {
            sroot = parent[sroot];
        }
        // recursively get the root of the union of end
        int eroot = end;
        while ( eroot != parent[eroot] ) {
            eroot = parent[eroot];
        }
        // ---------------- equivalent to union() function --------------------
        if ( sroot==eroot ) {
            // is already in the same union, current edge is redundant
            int[] ans= { start+1, end+1 };
            return ans;
        }else {
            // merge to the same union
            parent[eroot] = sroot;
        }
    }
    return null;
}

优化

路径优化 Path Compression

在find时使所有子节点均指向最终父节点,缩短递归查找父节点的路径
注:路径压缩并不会改变每个节点的秩

Path Compression

private int find(int[] parent, int p) {
    if( parent[i] == i ) {
        return parent[i];    
    }    
    parent[i] = find( parent, parent[i] );  //refresh value in the array before pass it to bottom
    return parent[i];
}

按秩合并 Union by Rank

在union时将节点少的树向节点(size)多的树合并。因为节点多少并不一定代表树的高度,所以可以进一步抽象化为将高度(rank,即这里的秩)小的树合并向高度大的树,以减少向上搜索的复杂度


Union by Size
//merge tree with smaller size to tree with larger size
// size[i] is initialized in main method to 1
public void union ( int[] parent, int[] size, int i, int j) {
    int ip = find ( parent, i );
    int jp = find ( parent, j );
    if ( ip != jp ) {
        if ( size[ip] > size[jp] ) {
            parent[ jp ] = ip; 
            size[ip] += size[jp];
        } else {
            parent[ ip ] = jp;
            size[jp] += size[ip];
        }
    }
}
Union By Rank
//merge tree with smaller height to tree with larger height
// rank[i] is initialized in main method to 1
public void union ( int[] parent, int[] rank, int i, int j) {
    int ip = find ( parent, i );
    int jp = find ( parent, j );
    if ( ip != jp ) {
        //only have to modify rank if the current two trees are with equivalent height
        if ( rank[ip] > rank[jp] ) {
            parent[ jp ] = ip; 
        } else if ( rank[jp] > rank[ip] ) {
            parent[ ip ] = jp;
        } else {
            parent[ jp ] = ip; 
            rank[ip]++;
        }
    }
}

复杂度

N = 需要合并的节点数
空间:O(N),建立了与节点树等长的parent数组
时间: 当同时使用了路径压缩和按秩合并后,合并的时间复杂度为O(N\alpha(N))O(N)。单次操作复杂度为O(\alpha(N))≈1。其中\alpha是Inverse-Ackerman函数。如果只有路径压缩或按秩合并,单次操作复杂度为O(log{N})

相关练习

Leetcode CN 684 - 冗余连接
Leetcode CN 547 - 朋友圈
Leetcode CN 1202 - 交换字符串中的元素

参考文献

参考文章1

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