๐Ÿšถโ€โ™‚๏ธRoad to ์ฝ”ํ…Œ(14) - Union-Find

์•ˆํƒœํ˜„ยท2026๋…„ 2์›” 9์ผ

Algorithm

๋ชฉ๋ก ๋ณด๊ธฐ
15/15

Union-Find(=Disjoint Set Union, DSU)๋Š” โ€œ์„œ๋กœ์†Œ ์ง‘ํ•ฉโ€์„ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๋ฉด์„œ, ๋‘ ์›์†Œ๊ฐ€ ๊ฐ™์€ ์ง‘ํ•ฉ์ธ์ง€ ํ™•์ธ(Find)ํ•˜๊ณ  ๋‘ ์ง‘ํ•ฉ์„ ํ•ฉ์น˜๋Š”(Union) ์—ฐ์‚ฐ์„ ๋น ๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š” ์ž๋ฃŒ๊ตฌ์กฐ/์•Œ๊ณ ๋ฆฌ์ฆ˜์ด๋‹ค. ๊ฒฝ๋กœ ์••์ถ•(Path Compression)๊ณผ ๋žญํฌ/์‚ฌ์ด์ฆˆ ๊ธฐ๋ฐ˜ ํ•ฉ์น˜๊ธฐ(Union by Rank/Size)๋ฅผ ๊ฐ™์ด ์“ฐ๋ฉด, ์—ฐ์‚ฐ์ด ๊ฑฐ์˜ ์ƒ์ˆ˜ ์‹œ๊ฐ„์— ๊ฐ€๊น๊ฒŒ ๋™์ž‘ํ•œ๋‹ค.

Union-Find๊ฐ€ ํ•„์š”ํ•œ ์ˆœ๊ฐ„

๊ทธ๋ž˜ํ”„์—์„œ โ€œ์‚ฌ์ดํด์ด ์ƒ๊ธฐ๋Š”์ง€โ€ ํ™•์ธํ•˜๊ฑฐ๋‚˜, ์ตœ์†Œ ์‹ ์žฅ ํŠธ๋ฆฌ(MST)์ธ Kruskal ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ตฌํ˜„ํ•  ๋•Œ ์ž์ฃผ ๋“ฑ์žฅํ•œ๋‹ค. ํŠนํžˆ Kruskal์—์„œ๋Š” ๊ฐ„์„ ์„ ๊ฐ€์ค‘์น˜ ์ˆœ์œผ๋กœ ๋ณด๋ฉฐ, ๋‘ ์ •์ ์ด ์ด๋ฏธ ๊ฐ™์€ ์ง‘ํ•ฉ์ด๋ฉด ๊ทธ ๊ฐ„์„ ์„ ์ถ”๊ฐ€ํ•  ๋•Œ ์‚ฌ์ดํด์ด ์ƒ๊ธฐ๋ฏ€๋กœ ์ œ์™ธํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ Union-Find๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

ํ•ต์‹ฌ ์—ฐ์‚ฐ 2๊ฐ€์ง€

  • Find(x): x๊ฐ€ ์†ํ•œ ์ง‘ํ•ฉ์˜ ๋Œ€ํ‘œ(๋ฃจํŠธ)๋ฅผ ์ฐพ๋Š”๋‹ค.
  • Union(a, b): a๊ฐ€ ์†ํ•œ ์ง‘ํ•ฉ๊ณผ b๊ฐ€ ์†ํ•œ ์ง‘ํ•ฉ์„ ํ•ฉ์นœ๋‹ค(์ด๋ฏธ ๊ฐ™์œผ๋ฉด no-op์ด๋‹ค).

์—ฌ๊ธฐ์„œ DSU๋Š” parent ๋ฐฐ์—ด๋กœ ํŠธ๋ฆฌ(ํฌ๋ ˆ์ŠคํŠธ)๋ฅผ ํ‘œํ˜„ํ•˜๋Š” ๋ฐฉ์‹์ด ์ผ๋ฐ˜์ ์ด๊ณ , Find๋Š” ํŠธ๋ฆฌ์˜ ๋ฃจํŠธ๊นŒ์ง€ ๋”ฐ๋ผ ์˜ฌ๋ผ๊ฐ€๋Š” ๊ณผ์ •์ด๋‹ค.

์ตœ์ ํ™” 2๊ฐ€์ง€ (์ค‘์š”)

  • Path Compression: Find๋ฅผ ์ˆ˜ํ–‰ํ•  ๋•Œ, ๊ฑฐ์ณ๊ฐ„ ๋…ธ๋“œ๋“ค์˜ parent๋ฅผ ๋ฃจํŠธ๋กœ ์ง์ ‘ ์—ฐ๊ฒฐํ•ด ๋‹ค์Œ Find๋ฅผ ๋น ๋ฅด๊ฒŒ ๋งŒ๋“ ๋‹ค.
  • Union by Rank: ๋‘ ํŠธ๋ฆฌ๋ฅผ ํ•ฉ์น  ๋•Œ ๋žญํฌ(๋Œ€๋žต ๋†’์ด)๊ฐ€ ๋‚ฎ์€ ์ชฝ์„ ๋†’์€ ์ชฝ ๋ฐ‘์— ๋ถ™์—ฌ ํŠธ๋ฆฌ๊ฐ€ ํ•œ์ชฝ์œผ๋กœ ๊ธธ์–ด์ง€๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•œ๋‹ค.

Java ๊ตฌํ˜„ (Path Compression + Union by Rank)

์•„๋ž˜ ์ฝ”๋“œ๋Š” ์ฝ”๋”ฉํ…Œ์ŠคํŠธ์—์„œ ๋ฐ”๋กœ ๊ฐ€์ ธ๋‹ค ์“ฐ๊ธฐ ์ข‹์€ ํ˜•ํƒœ(0..n-1 ์ •์ )๋กœ ์ž‘์„ฑํ•œ๋‹ค.

public class UnionFind {
    private final int[] parent;
    private final int[] rank; // ํŠธ๋ฆฌ์˜ ๋Œ€๋žต์  ๋†’์ด๋‹ค.

    public UnionFind(int n) {
        parent = new int[n];
        rank = new int[n];
        for (int i = 0; i < n; i++) parent[i] = i;
    }

    // Find with path compression์ด๋‹ค.
    public int find(int x) {
        if (parent[x] == x) return x;
        parent[x] = find(parent[x]); // path compression์ด๋‹ค.
        return parent[x];
    }

    // Union by rank์ด๋‹ค.
    public boolean union(int a, int b) {
        int ra = find(a);
        int rb = find(b);
        if (ra == rb) return false;

        if (rank[ra] < rank[rb]) {
            parent[ra] = rb;
        } else if (rank[ra] > rank[rb]) {
            parent[rb] = ra;
        } else {
            parent[rb] = ra;
            rank[ra]++;
        }
        return true;
    }

    public boolean isSameSet(int a, int b) {
        return find(a) == find(b);
    }
}

์˜ˆ์‹œ: ์‚ฌ์ดํด ํŒ๋ณ„(๋ฌด๋ฐฉํ–ฅ ๊ทธ๋ž˜ํ”„)

๋ฌด๋ฐฉํ–ฅ ๊ทธ๋ž˜ํ”„์—์„œ ๊ฐ„์„  (u, v)๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค๋Š”๋ฐ u์™€ v๊ฐ€ ์ด๋ฏธ ๊ฐ™์€ ์ง‘ํ•ฉ์ด๋ฉด ๊ทธ ๊ฐ„์„ ์€ ์‚ฌ์ดํด์„ ๋งŒ๋“ ๋‹ค.

// edges: int[][] edges = { {0,1}, {1,2}, {2,0} ... }์ด๋‹ค.
UnionFind uf = new UnionFind(n);

boolean hasCycle = false;
for (int[] e : edges) {
    int u = e[0], v = e;
    if (uf.isSameSet(u, v)) { // ์ด๋ฏธ ์—ฐ๊ฒฐ๋จ => ์ถ”๊ฐ€ํ•˜๋ฉด ์‚ฌ์ดํด์ด๋‹ค.
        hasCycle = true;
        break;
    }
    uf.union(u, v);
}
profile
๊ณ ๊ฐ์—๊ฒŒ ์‹ ๋ขฐ๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž ์•ˆํƒœํ˜„์ž…๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€