Himpunan Lepas (UFDS)

1. Introduction

Struktur data Himpunan Lepas (UFDS) digunakan untuk memodelkan sebuah kumpulan dari himpunan-himpunan yang saling lepas, dan dapat menentukan di himpunan mana sebuah item berada, mengecek apakah dua item-item berasal dari himpunan yang sama, dan menggabungkan dua himpunan berbeda menjadi satu jika diperlukan. Himpunan Lepas dapat digunakan untuk menemukan komponen yang terhubung dalam sebuah graf tak berarah, sehingga dapat digunakan sebagai bagian dari algoritma Kruskal untuk masalah Pohon Perentang Minimum (MST).

2. Visualisasi

Lihatlah visualisasi dari contoh UFDS (Himpunan Lepas) di sini!


Setiap pohon melambangkan sebuah himpunan lepas (maka sebuah kumpulan dari himpunan-himpunan lepas tersebut membentuk sebuah hutan) dan akar (root) dari setiap pohon adalah item representatif dari himpunan lepas ini.


Sekarang berhenti dan lihatlah pohon-pohon yang sekarang sedang divisualisasikan. Ada berapa jumlah item-item disana? Berapa jumlah himpunan lepas di sana? Siapa saja anggota dari setiap himpunan lepas tersebut? Apakah item representasi dari setiap himpunan lepas yang ada?

2-1. Titik pengecekan 1

Karena kami menetapkan contoh default untuk kuliah maya ini, jawaban-jawaban anda harusnya: N=13 dan ada 4 himpunan lepas: {0,1,2,3,4,10}, {5,7,8,11}, {6,9}, {12} dengan anggota-anggota yang digaris bawahi adalah item-item representatif (dari himpunan lepas mereka).

2-2. Menyimpan Data - Bagian 1

We can simply record this forest of trees with an array p of size N items where p[i] records the parent of item i and if p[i] = i, then i is the root of this tree and also the representative item of the set that contains item i.


Once again, look at the visualization above and determine the values inside this array p.


Discuss: If i is the root of the tree that contains it, can we set p[i] = -1 instead of p[i] = i? What are the implications?

2-3. The Implications

[This is a hidden slide]

2-4. Titik pengecekan 2

Pada contoh tetap yang sama, jawaban-jawaban anda harusnya p = [1, 3, 3, 3, 3, 5, 6, 5, 5, 6, 4, 8,12] dengan ukuran N = 13 untuk p[0] sampai p[12].


Anda dapat mengecek bahwa p[3] = 3p[5] = 5p[6] = 6, dan p[12] = 12, yang adalah konsisten dengan fakta bahwa {3,5,6,12} adalah item-item representatif (dari himpunan lepas mereka sendiri).

2-5. Menyimpan Data - Bagian 2

Kita juga menyimpan satu lagi informasi di dalam larik rank juga dengan ukuran N. Nilai dari rank[i] adalah batas-atas dari tinggi sub-pohon yang berakar pada simpul i yang akan digunakan sebagai heuristik pembimbing untuk operasi UnionSet(i, j). Anda akan menyadari nanti bahwa setelah heuristik 'kompresi-jalur' (akan dijelaskan segera) mengkompres sebuah jalur, nilai-nilai peringkat tidak lagi merefleksikan tinggi sesungguhnya dari sub-pohon tersebut.


Karena banyak item-item dengan peringkat 0, kami mengatur visualisasi sebagai berikut untuk mengurangi kekacauan: Hanya jika peringkat dari sebuah simpul i lebih besar dari 0, maka VisuAlgo akan menunjukkan nilai dari rank[i] (disingkat sebagai satu karakter r) sebagai teks berwarna merah dibawah simpul i.

2-6. Titik pengecekan 3

Pada contoh tetap yang sama, verifikasi bahwa {1,4,6,8} memiliki peringkat 1 dan {3,5} memiliki peringkat 2, dan yang lainnya memiliki peringkat 0 (tidak ditunjukkan).


Pada saat ini, semua nilai-nilai peringkat adalah benar, yaitu mereka benar-benar mendeskripsikan tinggi dari sub-pohon yang berakar pada simpul tersebut. Kita akan segera melihat bahwa mereka tidak akan selalu benar di beberapa slide-slide berikutnya.

3. Operasi-operasi

Terdapat lima operasi-operasi UFDS (Himpunan Lepas) dalam halaman visualisasi ini:
Contoh-Contoh, Inisialisasi(N), FindSet(i), IsSameSet(i, j), dan UnionSet(i, j).


Operasi pertama (Contoh-Contoh) adalah sederhana: Berikan daftar struktur-struktur Himpunan Lepas dengan berbagai karakteristik-karakteristik untuk titik permulaan anda. Mode kuliah maya ini selalu menggunakan contoh 'Empat Himpunan Lepas' sebagai titik permulaan.


Juga sadari bahwa tidak ada satupun dari contoh-contoh yang memiliki 'pohon yang tinggi'. Anda akan segera mengerti alasannya setelah kami menjelaskan dua heuristik-heuristik yang dipakai.

4. Initialize(N, M)

Initialize(N, M): Create N items and form M disjoint sets with these N items. We randomly pick two disjoint sets and merge them until we have M random disjoint sets. Currently this setup is not random enough, i.e., it cannot create tall trees for example.


The default form is Initialize(N, N), i.e., M = N, all with p[i] = i and rank[i] = 0 (all these rank values are initially not shown). The time complexity of this operation is clearly O(N).


Due to the limitation of screen size, we set 1 ≤ N ≤ 16.

5. FindSet(i)

FindSet(i): Dari simpul i, pergi ke arah atas di dalam pohon secara rekursif. Yaitu, dari simpul i, kita pergi ke simpul p[i]) hingga kita sampai pada akar dari pohon tersebut, yang adalah item representasi dengan p[i] = i dari himpunan lepas ini.


Dalam operasi FindSet(i), kami menggunakan heuristik kompresi-jalur setelah setiap panggilan kepada FindSet(i) karena sekarang setiap simpul yang terdapat dalam jalur dari simpul i ke akar dari pohon ini mengetahui bahwa akar tersebut adalah item representatif mereka dan dapat langsung menunjuk kepada akar tersebut secara langsung dalam O(1).

5-1. Contoh-Contoh Praktis

If we execute FindSet(12), we will immediately get vertex 12.
If we execute FindSet(9) we will get vertex 6 after 1 step and no other change.


Now try executing FindSet(0). If this is your first call on this default UFDS example, it will return vertex 3 after 2 steps and then modify the underlying UFDS structure due to path-compression in action (that is, vertex 0 points to vertex 3 directly). Notice that rank value of rank[1] = 1 is now wrong as vertex 1 becomes a new leaf. However, we will not bother to update its value.


Notice that the next time you execute FindSet(0) again, it will be (much) faster as the path has been compressed. For now, we assume that FindSet(i) runs in O(1).

6. IsSameSet(i, j)

IsSameSet(i, j): Cek saja apakah FindSet(i) == FindSet(j). Fungsi ini digunakan secara ektensif pada algoritma MST Kruskal. Karena fungsi ini hanya memanggil operasi FindSet dua kali, kita akan mengasumsikan bahwa fungsi ini juga berjalan dalam O(1).


Perlu diingat bahwa fungsi FindSet dipanggil di dalam fungsi isSameSet, maka heuristik kompresi-jalur juga digunakan secara tidak langsung.

6-1. Contoh-Contoh Praktis

Jika kita memanggil IsSameSet(3, 5), kita harusnya mendapatkan false karena simpul 3 dan simpul 5 adalah item-item representatif dari himpunan-himpunan lepas mereka dan mereka berbeda.


Sekarang cobalah IsSameSet(0, 11) padah contoh default yang sama untuk melihat kompresi-jalur secara tidak langsung pada simpul 0 dan simpul 11. Kita harusnya mendapatkan false karena dua item-item representatif: simpul 3 dan simpul 5, adalah berbeda. Sadari bahwa nilai-nilai peringkat pada simpul {1, 5, 8} sekarang semuanya salah. Tetapi kita tidak akan memperbaikinya.

7. UnionSet(i, j)

UnionSet(i, j): If item i and j come from two disjoint sets initially, we link the representative item of the shorter tree/disjoint set to the representative item of the taller tree/disjoint set (otherwise, we do nothing). This is also done in O(1).


This is union-by-rank heuristic in action and will cause the resulting tree to be relatively short. Only if the two trees are equally tall before union (by comparing their rank values heuristically — note that we are not comparing their actual — the current — heights), then the rank of the resulting tree will increase by one unit.

7-1. Kompresi Jalur Tidak-langsung

Catat juga bahwa fungsi FindSet dipanggil dari fungsi UnionSet, jadi heuristik kompresi-jalur juga secara tidak langsung dipakai. Setiap kali heuristik kompresi-jalur mengkompres sebuah jalur, setidaknya satu dari nilai peringkat akan menjadi salah. Kita tidak perlu memperbaiki nilai-nilai peringkat ini karena mereka hanya dipakai sebagai heuristik pembimbing untuk fungsi UnionSet ini.

7-2. Contoh-Contoh Praktis

On the same default example, try UnionSet(9, 12). As the tree that represents disjoint set {6, 9} is currently taller (according to the value of rank[6] = 1), then the shorter tree that represents disjoint set {12} will be slotted under vertex 6, without increasing the height of the combined tree at all.


On the same default example, try UnionSet(0, 11). Notice that the ranks of vertex 3 and vertex 5 are the same rank[3] = rank[5] = 2. Thus, we can either put vertex 3 under vertex 5 (our implementation) or vertex 5 under vertex 3 (both will increase the resulting height of the combined tree by 1). Notice the indirect path-compression heuristic in action.

7-3. Kuis-Kuis Mini

Quiz: Starting with N=8 disjoint sets, how tall (heuristically) can the resulting final tree if we call 7 UnionSet(i, j) operations strategically?

rank:3
rank:4
rank:1
rank:2
rank:5

Quiz: Starting with N=8 disjoint sets, how short (heuristically) can the resulting final tree if we call 7 UnionSet(i, j) operations strategically?

rank:4
rank:1
rank:5
rank:2
rank:3


Diskusi: Kenapa?

7-4. Jawabannya

[This is a hidden slide]

8. Kompleksitas-Kompleksitas Waktu Sesungguhnya

So far, we say that FindSet(i), IsSameSet(i, j), and UnionSet(i, j) runs in O(1). Actually they run in O(α(N)) if the UFDS is implemented with both path-compression and union-by-rank heuristics. The analysis is quite involved and is skipped in this visualization.


This α(N) is called the inverse Ackermann function that grows extremely slowly. For practical usage of this UFDS data structure (assuming N ≤ 1M), we have α(1M) ≈ 1.

9. Tambahan

Anda telah mencapai akhir dari informasi mendasar mengenai struktur data Himpunan Lepas dan kami mendorong anda untuk pergi ke Mode Eksplorasi dan mengeksplorasi struktur data mudah tapi menarik ini menggunakan contoh-contoh anda sendiri.

Akan tetapi, kami masih memiliki tantangan-tantangan Himpunan Lepas yang lebih menarik untuk anda.

9-1. Source Code

Lihatlah implementasi-implementasi dari struktur data Himpunan Lepas ini dalam bahasa C++/Python/Java/OCaml dalam format Pemograman Berorientasi Objek (OOP)unionfind_ds.cpp | py | java | ml).

Anda bebas memodifikasi implementasi ini sesuai dengan kebutuhan anda karena beberapa soal-soal yang lebih sulit memerlukan pengubahan atas implementasi dasar ini.

Saya berharap suatu hari C++/Python/Java/OCaml/bahasa-bahasa pemrograman lainnya akan memasukkan struktur data menarik ini ke Java akan memasukkan struktur data menarik ini dalam perpustakaan dasar mereka.

9-2. Kuis Online

For a few more interesting questions about this data structure, please practice on Union-Find Disjoint Sets training module.

9-3. Soal-soal Online Judge

Even after clearing the Online Quiz of this UFDS module, do you think that you have really mastered this data structure?


Let us challenge you by asking you to solve two programming problems that somewhat requires the usage of this Union-Find Disjoint Sets data structure: UVa 01329 - Corporative Network and Kattis - control.


Beware that both problems are actual International Collegiate Programming Contest (ICPC) problems, i.e., they are "not trivial".

9-4. The Hints

[This is a hidden slide]

9-5. Union, Find, de-Union?

Notice that there is no 'undo' operation for Union-Find Disjoint Sets (UFDS) data structure. Once two initially disjoint sets were union-ed, it is not easy to split them back into the original two disjoint sets, especially when path compressions have flattened the combined tree.


Discussion: So what to do if we need this 'de-Union' or 'split' or 'cut' operation?

9-6. The Answer

[This is a hidden slide]