ExcelVBAでUnion-Findクラスを作成
Union-Findクラスの作成(UnionFindClass.cls)
Option Explicit
Private n As Long '要素数
Private parents() As Variant '要素の配列
Private i As Long 'ループカウンタ
' クラスの初期化
Public Sub Initialize(ByVal c_n As Long)
n = c_n
ReDim parents(n - 1) '配列の要素数nに変更
For i = 0 To n - 1 '配列parentsを初期化
parents(i) = -1
Next
End Sub
' 要素xが属するグループと要素yが属するグループを接合する
Public Sub Union(ByVal c_x As Long, ByVal c_y As Long)
Dim x As Long
Dim y As Long
Dim wk As Long ' 作業用変数
x = find(c_x)
y = find(c_y)
If x = y Then
Exit Sub
End If
If parents(x) > parents(y) Then ' xとyを入れ替える
wk = x
x = y
y = wk
End If
parents(x) = parents(x) + parents(y)
parents(y) = x
End Sub
'要素xが属するグループの根を返す
Public Function find(ByVal c_x As Long) As Long
If parents(c_x) < 0 Then
find = c_x
Else
parents(c_x) = find(parents(c_x))
find = parents(c_x)
End If
End Function
'要素xの属するグループの大きさを返す
Public Function size(ByVal c_x As Long) As Long
size = -parents(Me.find(c_x))
End Function
'要素xと要素yが同じグループに属しているか判定する
Public Function same(ByVal c_x As Long, ByVal c_y As Long) As Boolean
If Me.find(c_x) = Me.find(c_y) Then
same = True
Else
same = False
End If
End Function
'要素xが属するグループの要素をリストで返す
Public Function members(ByVal c_x As Long) As Variant()
Dim root As Long ' 要素xが属するグループの根
Dim rtn_members() As Variant ' 戻り値の配列
root = find(c_x) ' 要素xが属するグループの根
For i = 0 To n - 1 '要素数分繰り返す
If find(i) = root Then ' 各要素が要素xが属するグループの根と同じ場合
ReDim Preserve rtn_members(i) '配列の大きさを変更、Preserveで元のデータを残す
rtn_members(i) = i '戻り値の配列に要素iを追加
End If
Next
members = rtn_members()
End Function
'その木に属するすべての根の要素をリストで返す
Public Function roots() As Variant()
Dim rtn_roots() As Variant ' 戻り値の配列
For i = 0 To n - 1 '要素数分繰り返す
If parents(i) < 0 Then ' 各要素が負の場合(=グループの根)
ReDim Preserve rtn_roots(i) '配列の大きさを変更、Preserveで元のデータを残す
rtn_roots(i) = i '戻り値の配列に要素iを追加
End If
Next
roots = rtn_roots()
End Function
'その木のグループの数を返す
Public Function group_count() As Long
group_count = UBound(roots(), 1)
End Function
' n の値を取得
Property Get get_n() As Long
get_n = n
End Property
Property Get get_parents() As Variant ' parents() の値を取得
get_parents = parents()
End Property
UnionFindClassのテスト(UnionFindTest.bas)
Option Explicit
Sub UnionFind_test()
'
' https://qiita.com/uniTM/items/77ef2412e426cae44586
' のテスト
'
Dim uf_3 As UnionFindClass
Dim uf_5 As UnionFindClass
Dim i As Long
Dim output As String ' 出力文字列
Dim parents As Variant
'
' parentsのテスト
'
Debug.Print vbCrLf & "parentsのテスト"
Set uf_3 = New UnionFindClass
Call uf_3.Initialize(3) 'UnionFindClassの初期化
Debug.Print "uf_3.parents() = ";
Print_Array (uf_3.get_parents()) ' Debug.Printで配列を一括出力
Set uf_5 = New UnionFindClass
Call uf_5.Initialize(5) 'UnionFindClassの初期化
Debug.Print vbCrLf & "uf_5.parents() = ";
Print_Array (uf_5.get_parents()) ' Debug.Printで配列を一括出力
'
' union(x, y)のテスト
'
Debug.Print vbCrLf
Debug.Print vbCrLf & "union(x, y)のテスト"
Call uf_3.Union(1, 2) 'グループの接合
Debug.Print "uf_3.parents() = ";
Print_Array (uf_3.get_parents()) ' Debug.Printで配列を一括出力
Call uf_3.Union(0, 1) 'グループの接合
Debug.Print vbCrLf & "uf_3.parents() = ";
Print_Array (uf_3.get_parents()) ' Debug.Printで配列を一括出力
Call uf_5.Union(1, 2) 'グループの接合
Debug.Print vbCrLf & "uf_5.parents() = ";
Print_Array (uf_5.get_parents()) ' Debug.Printで配列を一括出力
Call uf_5.Union(2, 4) 'グループの接合
Debug.Print vbCrLf & "uf_5.parents() = ";
Print_Array (uf_5.get_parents()) ' Debug.Printで配列を一括出力
'
' Find(x)のテスト
'
Debug.Print vbCrLf
Debug.Print vbCrLf & "Find(x)のテスト"
Debug.Print "uf_3.Find(2) = " & uf_3.find(2) ' 0とunion()したので、親は1
Debug.Print "uf_3.Find(1) = " & uf_3.find(1) ' 1の親はもちろん1
Debug.Print "uf_5.Find(3) = " & uf_5.find(3) ' 3はどれともつながっていないので、3
Debug.Print "uf_5.Find(4) = " & uf_5.find(4) ' union(1, 2)とunion(2, 4)より親は1
'
' Size(x)のテスト
'
Debug.Print vbCrLf & "Size(x)のテスト"
Debug.Print "uf_3.Size(2) = " & uf_3.size(2)
Debug.Print "uf_3.Size(1) = " & uf_3.size(1)
Debug.Print "uf_5.Size(3) = " & uf_5.size(3)
Debug.Print "uf_5.Size(4) = " & uf_5.size(4)
'
' same(x,y)のテスト
'
Debug.Print vbCrLf & "same(x,y)のテスト"
Debug.Print "uf_3.same(1, 2) = " & uf_3.same(1, 2)
Debug.Print "uf_3.same(0, 2) = " & uf_3.same(0, 2)
Debug.Print "uf_5.same(1, 4) = " & uf_5.same(1, 4)
Debug.Print "uf_5.same(1, 3) = " & uf_5.same(1, 3)
'
' members()のテスト
'
Debug.Print vbCrLf & "members()のテスト"
Debug.Print vbCrLf & "uf_3.members(0) = ";
Print_Array (uf_3.members(0)) ' Debug.Printで配列を一括出力
Debug.Print vbCrLf & "uf_3.members(1) = ";
Print_Array (uf_3.members(1)) ' Debug.Printで配列を一括出力
Debug.Print vbCrLf & "uf_5.members(1) = ";
Print_Array (uf_5.members(1)) ' Debug.Printで配列を一括出力
Debug.Print vbCrLf & "uf_5.members(3) = ";
Print_Array (uf_5.members(3)) ' Debug.Printで配列を一括出力
'
' roots()のテスト
'
Debug.Print vbCrLf
Debug.Print vbCrLf & "roots()のテスト"
Debug.Print vbCrLf & "uf_3.roots() = ";
Print_Array (uf_3.roots()) ' Debug.Printで配列を一括出力
Debug.Print vbCrLf & "uf_5.roots() = ";
Print_Array (uf_5.roots()) ' Debug.Printで配列を一括出力
'
' group_count()のテスト
'
Debug.Print vbCrLf
Debug.Print vbCrLf & "group_count()のテスト"
Debug.Print "uf_3.group_count() = " & uf_3.group_count()
Debug.Print "uf_5.group_count() = " & uf_5.group_count()
End Sub
Sub UnionFind_test2()
'
' https://note.nkmk.me/python-union-find/
' のテスト
'
Dim uf_6 As UnionFindClass
Debug.Print vbCrLf & "https://note.nkmk.me/python-union-find/のテスト"
Set uf_6 = New UnionFindClass
Call uf_6.Initialize(6) 'UnionFindClassの初期化
Debug.Print vbCrLf & "uf_6の初期化"
Debug.Print "uf_6.parents() = ";
Print_Array (uf_6.get_parents()) ' Debug.Printで配列を一括出力
Debug.Print vbCrLf & vbCrLf & "グループの接合 uf_6.Union(0, 2)"
Call uf_6.Union(0, 2) 'グループの接合
Debug.Print "uf_6.parents() = ";
Print_Array (uf_6.get_parents()) ' Debug.Printで配列を一括出力
Debug.Print vbCrLf & vbCrLf & "グループの接合 uf_6.Union(1, 3)"
Call uf_6.Union(1, 3) 'グループの接合
Debug.Print "uf_6.parents() = ";
Print_Array (uf_6.get_parents()) ' Debug.Printで配列を一括出力
Debug.Print vbCrLf & vbCrLf & "グループの接合 uf_6.Union(4, 5)"
Call uf_6.Union(4, 5) 'グループの接合
Debug.Print "uf_6.parents() = ";
Print_Array (uf_6.get_parents()) ' Debug.Printで配列を一括出力
Debug.Print vbCrLf & vbCrLf & "グループの接合 uf_6.Union(1, 4)"
Call uf_6.Union(1, 4) 'グループの接合
Debug.Print "uf_6.parents() = ";
Print_Array (uf_6.get_parents()) ' Debug.Printで配列を一括出力
Debug.Print vbCrLf & vbCrLf & "グループの根を返す"
Debug.Print "uf_6.Find(5) = " & uf_6.find(5) '要素xが属するグループの根を返す
'find()を実行すると経路圧縮により親要素が根要素に更新
Debug.Print "uf_6.parents() = ";
Print_Array (uf_6.get_parents()) ' Debug.Printで配列を一括出力
End Sub
実行結果(イミディエイトウィンドウに出力)
parentsのテスト
uf_3.parents() = -1 -1 -1
uf_5.parents() = -1 -1 -1 -1 -1
union(x, y)のテスト
uf_3.parents() = -1 -2 1
uf_3.parents() = 1 -3 1
uf_5.parents() = -1 -2 1 -1 -1
uf_5.parents() = -1 -3 1 -1 1
Find(x)のテスト
uf_3.Find(2) = 1
uf_3.Find(1) = 1
uf_5.Find(3) = 3
uf_5.Find(4) = 1
Size(x)のテスト
uf_3.Size(2) = 3
uf_3.Size(1) = 3
uf_5.Size(3) = 1
uf_5.Size(4) = 3
same(x,y)のテスト
uf_3.same(1, 2) = True
uf_3.same(0, 2) = True
uf_5.same(1, 4) = True
uf_5.same(1, 3) = False
members()のテスト
uf_3.members(0) = 0 1 2
uf_3.members(1) = 0 1 2
uf_5.members(1) = 1 2 4
uf_5.members(3) = 3
roots()のテスト
uf_3.roots() = 1
uf_5.roots() = 0 1 3
group_count()のテスト
uf_3.group_count() = 1
uf_5.group_count() = 3
https://note.nkmk.me/python-union-find/のテスト
uf_6の初期化
uf_6.parents() = -1 -1 -1 -1 -1 -1
グループの接合 uf_6.Union(0, 2)
uf_6.parents() = -2 -1 0 -1 -1 -1
グループの接合 uf_6.Union(1, 3)
uf_6.parents() = -2 -2 0 1 -1 -1
グループの接合 uf_6.Union(4, 5)
uf_6.parents() = -2 -2 0 1 -2 4
グループの接合 uf_6.Union(1, 4)
uf_6.parents() = -2 -4 0 1 1 4
グループの根を返す
uf_6.Find(5) = 1
uf_6.parents() = -2 -4 0 1 1 1
参考サイト
Algorithm | Union-FindをPython3で解説(例題あり) https://qiita.com/uniTM/items/77ef2412e426cae44586
PythonでのUnion-Find(素集合データ構造)の実装と使い方
https://note.nkmk.me/python-union-find/