Skip to content

Commit d87575f

Browse files
committed
Add disjoint set
1 parent 9d2b124 commit d87575f

File tree

3 files changed

+210
-0
lines changed

3 files changed

+210
-0
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import DisjointSetItem from './DisjointSetItem';
2+
3+
export default class DisjointSet {
4+
/**
5+
* @param {function(value: *)} [keyCallback]
6+
*/
7+
constructor(keyCallback) {
8+
this.keyCallback = keyCallback;
9+
this.items = {};
10+
}
11+
12+
/**
13+
* @param {*} itemValue
14+
* @return {DisjointSet}
15+
*/
16+
makeSet(itemValue) {
17+
const disjointSetItem = new DisjointSetItem(itemValue, this.keyCallback);
18+
19+
if (!this.items[disjointSetItem.getKey()]) {
20+
// Thêm chỉ số mới trong trường hợp nó chưa được biểu diễn.
21+
this.items[disjointSetItem.getKey()] = disjointSetItem;
22+
}
23+
24+
return this;
25+
}
26+
27+
/**
28+
* Tìm nút đại diện tập hợp.
29+
*
30+
* @param {*} itemValue
31+
* @return {(string|null)}
32+
*/
33+
find(itemValue) {
34+
const templateDisjointItem = new DisjointSetItem(itemValue, this.keyCallback);
35+
36+
// Tự tìm mục;
37+
const requiredDisjointItem = this.items[templateDisjointItem.getKey()];
38+
39+
if (!requiredDisjointItem) {
40+
return null;
41+
}
42+
43+
return requiredDisjointItem.getRoot().getKey();
44+
}
45+
46+
/**
47+
* Hợp theo cấp bậc.
48+
*
49+
* @param {*} valueA
50+
* @param {*} valueB
51+
* @return {DisjointSet}
52+
*/
53+
union(valueA, valueB) {
54+
const rootKeyA = this.find(valueA);
55+
const rootKeyB = this.find(valueB);
56+
57+
if (rootKeyA === null || rootKeyB === null) {
58+
throw new Error('One or two values are not in sets');
59+
}
60+
61+
if (rootKeyA === rootKeyB) {
62+
// Trong trường hợp nếu cả hai phần tử nằm trong cùng một tập hợp thì chỉ cần trả về khóa của nó.
63+
return this;
64+
}
65+
66+
const rootA = this.items[rootKeyA];
67+
const rootB = this.items[rootKeyB];
68+
69+
if (rootA.getRank() < rootB.getRank()) {
70+
// Nếu cây của rootB lớn hơn thì đặt rootB là gốc mới.
71+
rootB.addChild(rootA);
72+
73+
return this;
74+
}
75+
76+
// Nếu cây của rootA lớn hơn thì đặt rootA là gốc mới.
77+
rootA.addChild(rootB);
78+
79+
return this;
80+
}
81+
82+
/**
83+
* @param {*} valueA
84+
* @param {*} valueB
85+
* @return {boolean}
86+
*/
87+
inSameSet(valueA, valueB) {
88+
const rootKeyA = this.find(valueA);
89+
const rootKeyB = this.find(valueB);
90+
91+
if (rootKeyA === null || rootKeyB === null) {
92+
throw new Error('One or two values are not in sets');
93+
}
94+
95+
return rootKeyA === rootKeyB;
96+
}
97+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
export default class DisjointSetItem {
2+
/**
3+
* @param {*} value
4+
* @param {function(value: *)} [keyCallback]
5+
*/
6+
constructor(value, keyCallback) {
7+
this.value = value;
8+
this.keyCallback = keyCallback;
9+
/** @var {DisjointSetItem} this.parent */
10+
this.parent = null;
11+
this.children = {};
12+
}
13+
14+
/**
15+
* @return {*}
16+
*/
17+
getKey() {
18+
// Cho phép người dùng định nghĩa khoá.
19+
if (this.keyCallback) {
20+
return this.keyCallback(this.value);
21+
}
22+
23+
// Nếu không thì sử dụng khoá mặc định.
24+
return this.value;
25+
}
26+
27+
/**
28+
* @return {DisjointSetItem}
29+
*/
30+
getRoot() {
31+
return this.isRoot() ? this : this.parent.getRoot();
32+
}
33+
34+
/**
35+
* @return {boolean}
36+
*/
37+
isRoot() {
38+
return this.parent === null;
39+
}
40+
41+
/**
42+
* Xếp hạng cơ bản dựa trên số lượng của các nhóm.
43+
* @return {number}
44+
*/
45+
getRank() {
46+
if (this.getChildren().length === 0) {
47+
return 0;
48+
}
49+
50+
let rank = 0;
51+
52+
/** @var {DisjointSetItem} child */
53+
this.getChildren().forEach((child) => {
54+
// Đếm số con của nó.
55+
rank += 1;
56+
57+
// Đồng thời thêm tất cả con vào con hiện tại.
58+
rank += child.getRank();
59+
});
60+
61+
return rank;
62+
}
63+
64+
/**
65+
* @return {DisjointSetItem[]}
66+
*/
67+
getChildren() {
68+
return Object.values(this.children);
69+
}
70+
71+
/**
72+
* @param {DisjointSetItem} parentItem
73+
* @param {boolean} forceSettingParentChild
74+
* @return {DisjointSetItem}
75+
*/
76+
setParent(parentItem, forceSettingParentChild = true) {
77+
this.parent = parentItem;
78+
if (forceSettingParentChild) {
79+
parentItem.addChild(this);
80+
}
81+
82+
return this;
83+
}
84+
85+
/**
86+
* @param {DisjointSetItem} childItem
87+
* @return {DisjointSetItem}
88+
*/
89+
addChild(childItem) {
90+
this.children[childItem.getKey()] = childItem;
91+
childItem.setParent(this, false);
92+
93+
return this;
94+
}
95+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Tập hợp không giao nhau
2+
3+
Cấu trúc dữ liệu **tập hợp không giao nhau** hay tập hợp rời rạc là một cấu trúc dữ liệu để lưu trữ một tập hợp các phần tử được phân chia thành nhiều tập hợp con không giao nhau.
4+
5+
Nó cung cấp các thao tác với thời gian gần như không đổi (được giới hạn bởi hàm ngược Ackermann) để *thêm các tập hợp mới*, *hợp nhất các tập hợp hiện có**xác định xem các phần tử có trong cùng một tập hợp hay không*. Ngoài ra còn nhiều công dụng khác (xem phần Ứng dụng), các tập hợp rời rạc đóng một vai trò quan trọng trong thuật toán Kruskal để tìm cây bao trùm nhỏ nhất của một đồ thị.
6+
7+
![disjoint set](https://upload.wikimedia.org/wikipedia/commons/6/67/Dsu_disjoint_sets_init.svg)
8+
9+
Thao tác *Tạo-Tập* tạo ra 8 phần tử.
10+
11+
![disjoint set](https://upload.wikimedia.org/wikipedia/commons/a/ac/Dsu_disjoint_sets_final.svg)
12+
13+
Sau một số thao tác *Hợp*, một số tập hợp đã được nhóm lại với nhau.
14+
15+
## Liên kết
16+
17+
- [Wikipedia](https://en.wikipedia.org/wiki/Disjoint-set_data_structure)
18+
- [By Abdul Bari on YouTube](https://www.youtube.com/watch?v=wU6udHRIkcc&index=14&t=0s&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8)

0 commit comments

Comments
 (0)