hirohirohirohirosのブログ

地方国立大学に通う情報系学部4年

Union Findをpythonで実装して関数化する

hirohirohirohiros.hatenablog.com

概要

 Union Findはグループ分けを管理するデータ構造です.要素aと要素bが同じグループに属しているかそうでないか判別することが出来ます.f:id:hirohirohirohiros:20220214003755p:plain
 また,複数のグループを一つのグループにくっつけることも出来ます.このようにUnion Findは複数機能を有しており,同じリストを使って管理するので,クラスを使って実装します.コードは蟻本を,クラスを使うというアイデアはnkmkさんのサイトhttps://note.nkmk.me/python-union-find/を参考にしました.

アルゴリズムの概要

併合,判定

 木構造を使います.n個の要素に対してUnion Findを使うとき,始めn個のノードを作ります.辺はありません.
 グループをくっつける(unite)には片方のグループの親からもう片方のグループの親に辺を張ればグループが一つになります.
 ノードaとノードbが同じグループに属してるか判定するにはそれぞれのグループの親を調べます.同じグループなら同じ親になるはずです.
 このことから,各ノードには親を記憶しておく必要があると分かります.親の管理にはリストを使います.リストのi番目はノードiの親の番号を記録すれば良いです.そして,リストのi番目の値とj番目の値が同じなら,ノードiとノードjは同じグループに属していることになります.uniteするにはどちらかの親をどちらかの親の値に書き換えれば良いです.どちらのノードの親を書き換えれば良いのでしょうか?より効率よくするためにrankという概念を導入します.

rank

 rankはグループの木の高さを表します.左のグループはrank1で右のグループはrank2です.rankが低い方が親を探すときに掛かる回数が少ないことが分かります.f:id:hirohirohirohiros:20220214005753p:plain
この二つのグループをuniteする時どちらのグループをどちらのグループにuniteすれば良いでしょうか?rankの小さい方をrankの大きい方にくっつければrankが増えずに済む事が分かります.f:id:hirohirohirohiros:20220214010509p:plain
よってグループにrankを記録することでより効率よくuniteが出来る事が分かります.

pythonコード

class Unionfind:
    """ Unionfind

    ノードのグループ化, 同一グループに属しているかの判定
    計算量: O(α(n)) log n より早い

    """
    def __init__(self, n):
        """

        Args:
            n (int): ノードの総数
        """
        self.par = [i for i in range(n)]
        self.rank = [0]*n

    def find(self, x):
        """親が何か探す

        Args:
            x (int): 親を探したいノード番号

        Returns:
            int: 親のノード番号
        """
        if self.par[x] == x:
            return x
        else:
            self.par[x] = self.find(self.par[x])
            return self.par[x]

    def unite(self, x, y):
        """グループの併合

        Args:
            x, y (int): 併合したいノードの番号
        """
        x = self.find(x)
        y = self.find(y)
        if x == y:
            return
        if self.rank[x] < self.rank[y]:
            self.par[x] = y
        else:
            self.par[y] = x
            if self.rank[x] == self.rank[y]:
                self.rank[x] += 1
    def same(self, x, y):
        """同じグループに属しているか判定

        Args:
            x, y (int): 同じグループに属しているか判定したいノードの番号

        Returns:
            bool: 同じグループならTrue, そうでないならFalse 
        """
        return self.find(x) == self.find(y)