mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-11-10 11:09:43 +08:00
Add disjoint set.
This commit is contained in:
parent
de5b771b4b
commit
2e76caa9d6
@ -20,6 +20,7 @@
|
||||
* Segment Tree or Interval Tree
|
||||
* Binary Indexed Tree or Fenwick Tree
|
||||
9. [Graph](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/graph) (both directed and undirected)
|
||||
9. [Disjoint Set](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/disjoint-set)
|
||||
|
||||
## Algorithms
|
||||
|
||||
|
93
src/data-structures/disjoint-set/DisjointSet.js
Normal file
93
src/data-structures/disjoint-set/DisjointSet.js
Normal file
@ -0,0 +1,93 @@
|
||||
import DisjointSetItem from './DisjointSetItem';
|
||||
|
||||
export default class DisjointSet {
|
||||
/**
|
||||
* @param {function(value: *)} [keyCallback]
|
||||
*/
|
||||
constructor(keyCallback) {
|
||||
this.keyCallback = keyCallback;
|
||||
this.items = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} itemValue
|
||||
* @return {DisjointSet}
|
||||
*/
|
||||
makeSet(itemValue) {
|
||||
const disjointSetItem = new DisjointSetItem(itemValue, this.keyCallback);
|
||||
|
||||
if (!this.items[disjointSetItem.getKey()]) {
|
||||
// Add new item only in case if it not presented yet.
|
||||
this.items[disjointSetItem.getKey()] = disjointSetItem;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} itemValue
|
||||
* @return {(string|null)}
|
||||
*/
|
||||
find(itemValue) {
|
||||
const templateDisjointItem = new DisjointSetItem(itemValue, this.keyCallback);
|
||||
|
||||
// Try to find item itself;
|
||||
const requiredDisjointItem = this.items[templateDisjointItem.getKey()];
|
||||
|
||||
if (!requiredDisjointItem) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return requiredDisjointItem.getRoot().getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} valueA
|
||||
* @param {*} valueB
|
||||
* @return {DisjointSet}
|
||||
*/
|
||||
union(valueA, valueB) {
|
||||
const rootKeyA = this.find(valueA);
|
||||
const rootKeyB = this.find(valueB);
|
||||
|
||||
if (rootKeyA === null || rootKeyB === null) {
|
||||
throw new Error('One or two values are not in sets');
|
||||
}
|
||||
|
||||
if (rootKeyA === rootKeyB) {
|
||||
// In case if both elements are already in the same set then just return its key.
|
||||
return this;
|
||||
}
|
||||
|
||||
const rootA = this.items[rootKeyA];
|
||||
const rootB = this.items[rootKeyB];
|
||||
|
||||
if (rootA.getAncestorsCount() < rootB.getAncestorsCount()) {
|
||||
// If rootB's tree is bigger then make rootB to be a new root.
|
||||
rootB.addChild(rootA);
|
||||
|
||||
return rootB.getKey();
|
||||
}
|
||||
|
||||
// If rootA's tree is bigger then make rootA to be a new root.
|
||||
rootA.addChild(rootB);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} valueA
|
||||
* @param {*} valueB
|
||||
* @return {boolean}
|
||||
*/
|
||||
inSameSet(valueA, valueB) {
|
||||
const rootKeyA = this.find(valueA);
|
||||
const rootKeyB = this.find(valueB);
|
||||
|
||||
if (rootKeyA === null || rootKeyB === null) {
|
||||
throw new Error('One or two values are not in sets');
|
||||
}
|
||||
|
||||
return rootKeyA === rootKeyB;
|
||||
}
|
||||
}
|
94
src/data-structures/disjoint-set/DisjointSetItem.js
Normal file
94
src/data-structures/disjoint-set/DisjointSetItem.js
Normal file
@ -0,0 +1,94 @@
|
||||
export default class DisjointSetItem {
|
||||
/**
|
||||
* @param {*} value
|
||||
* @param {function(value: *)} [keyCallback]
|
||||
*/
|
||||
constructor(value, keyCallback) {
|
||||
this.value = value;
|
||||
this.keyCallback = keyCallback;
|
||||
/** @var {DisjointSetItem} this.parent */
|
||||
this.parent = null;
|
||||
this.children = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {*}
|
||||
*/
|
||||
getKey() {
|
||||
// Allow user to define custom key generator.
|
||||
if (this.keyCallback) {
|
||||
return this.keyCallback(this.value);
|
||||
}
|
||||
|
||||
// Otherwise use value as a key by default.
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {DisjointSetItem}
|
||||
*/
|
||||
getRoot() {
|
||||
return this.isRoot() ? this : this.parent.getRoot();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
isRoot() {
|
||||
return this.parent === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {number}
|
||||
*/
|
||||
getAncestorsCount() {
|
||||
if (this.getChildren().length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
|
||||
/** @var {DisjointSetItem} child */
|
||||
this.getChildren().forEach((child) => {
|
||||
// Count child itself.
|
||||
count += 1;
|
||||
|
||||
// Also add all children of current child.
|
||||
count += child.getAncestorsCount();
|
||||
});
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {DisjointSetItem[]}
|
||||
*/
|
||||
getChildren() {
|
||||
return Object.values(this.children);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DisjointSetItem} parentItem
|
||||
* @param {boolean} forceSettingParentChild
|
||||
* @return {DisjointSetItem}
|
||||
*/
|
||||
setParent(parentItem, forceSettingParentChild = true) {
|
||||
this.parent = parentItem;
|
||||
if (forceSettingParentChild) {
|
||||
parentItem.addChild(this);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DisjointSetItem} childItem
|
||||
* @return {DisjointSetItem}
|
||||
*/
|
||||
addChild(childItem) {
|
||||
this.children[childItem.getKey()] = childItem;
|
||||
childItem.setParent(this, false);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
20
src/data-structures/disjoint-set/README.md
Normal file
20
src/data-structures/disjoint-set/README.md
Normal file
@ -0,0 +1,20 @@
|
||||
# Disjoint Set
|
||||
|
||||
**Disjoint-set** data structure (also called a union–find data structure or merge–find set) is a data
|
||||
structure that tracks a set of elements partitioned into a number of disjoint (non-overlapping) subsets.
|
||||
It provides near-constant-time operations (bounded by the inverse Ackermann function) to *add new sets*,
|
||||
to *merge existing sets*, and to *determine whether elements are in the same set*.
|
||||
In addition to many other uses (see the Applications section), disjoint-sets play a key role in Kruskal's algorithm for finding the minimum spanning tree of a graph.
|
||||
|
||||
![disjoint set](https://upload.wikimedia.org/wikipedia/commons/6/67/Dsu_disjoint_sets_init.svg)
|
||||
|
||||
*MakeSet* creates 8 singletons.
|
||||
|
||||
![disjoint set](https://upload.wikimedia.org/wikipedia/commons/a/ac/Dsu_disjoint_sets_final.svg)
|
||||
|
||||
After some operations of *Union*, some sets are grouped together.
|
||||
|
||||
## References
|
||||
|
||||
- [Wikipedia](https://en.wikipedia.org/wiki/Disjoint-set_data_structure)
|
||||
- [By Abdul Bari on YouTube](https://www.youtube.com/watch?v=wU6udHRIkcc)
|
140
src/data-structures/disjoint-set/__test__/DisjointSet.test.js
Normal file
140
src/data-structures/disjoint-set/__test__/DisjointSet.test.js
Normal file
@ -0,0 +1,140 @@
|
||||
import DisjointSet from '../DisjointSet';
|
||||
|
||||
describe('DisjointSet', () => {
|
||||
it('should throw error when trying to union and check not existing sets', () => {
|
||||
function mergeNotExistingSets() {
|
||||
const disjointSet = new DisjointSet();
|
||||
|
||||
disjointSet.union('A', 'B');
|
||||
}
|
||||
|
||||
function checkNotExistingSets() {
|
||||
const disjointSet = new DisjointSet();
|
||||
|
||||
disjointSet.inSameSet('A', 'B');
|
||||
}
|
||||
|
||||
expect(mergeNotExistingSets).toThrow();
|
||||
expect(checkNotExistingSets).toThrow();
|
||||
});
|
||||
|
||||
it('should do basic manipulations on disjoint set', () => {
|
||||
const disjointSet = new DisjointSet();
|
||||
|
||||
expect(disjointSet.find('A')).toBeNull();
|
||||
expect(disjointSet.find('B')).toBeNull();
|
||||
|
||||
disjointSet.makeSet('A');
|
||||
|
||||
expect(disjointSet.find('A')).toBe('A');
|
||||
expect(disjointSet.find('B')).toBeNull();
|
||||
|
||||
disjointSet.makeSet('B');
|
||||
|
||||
expect(disjointSet.find('A')).toBe('A');
|
||||
expect(disjointSet.find('B')).toBe('B');
|
||||
|
||||
disjointSet.makeSet('C');
|
||||
|
||||
expect(disjointSet.inSameSet('A', 'B')).toBeFalsy();
|
||||
|
||||
disjointSet.union('A', 'B');
|
||||
|
||||
expect(disjointSet.find('A')).toBe('A');
|
||||
expect(disjointSet.find('B')).toBe('A');
|
||||
expect(disjointSet.inSameSet('A', 'B')).toBeTruthy();
|
||||
expect(disjointSet.inSameSet('B', 'A')).toBeTruthy();
|
||||
expect(disjointSet.inSameSet('A', 'C')).toBeFalsy();
|
||||
|
||||
disjointSet.union('A', 'A');
|
||||
|
||||
disjointSet.union('B', 'C');
|
||||
|
||||
expect(disjointSet.find('A')).toBe('A');
|
||||
expect(disjointSet.find('B')).toBe('A');
|
||||
expect(disjointSet.find('C')).toBe('A');
|
||||
|
||||
expect(disjointSet.inSameSet('A', 'B')).toBeTruthy();
|
||||
expect(disjointSet.inSameSet('B', 'C')).toBeTruthy();
|
||||
expect(disjointSet.inSameSet('A', 'C')).toBeTruthy();
|
||||
|
||||
disjointSet
|
||||
.makeSet('E')
|
||||
.makeSet('F')
|
||||
.makeSet('G')
|
||||
.makeSet('H')
|
||||
.makeSet('I');
|
||||
|
||||
disjointSet
|
||||
.union('E', 'F')
|
||||
.union('F', 'G')
|
||||
.union('G', 'H')
|
||||
.union('H', 'I');
|
||||
|
||||
expect(disjointSet.inSameSet('A', 'I')).toBeFalsy();
|
||||
expect(disjointSet.inSameSet('E', 'I')).toBeTruthy();
|
||||
|
||||
disjointSet.union('I', 'C');
|
||||
|
||||
expect(disjointSet.find('I')).toBe('E');
|
||||
expect(disjointSet.inSameSet('A', 'I')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should union smaller set with bigger one making bigger one to be new root', () => {
|
||||
const disjointSet = new DisjointSet();
|
||||
|
||||
disjointSet
|
||||
.makeSet('A')
|
||||
.makeSet('B')
|
||||
.makeSet('C')
|
||||
.union('B', 'C')
|
||||
.union('A', 'C');
|
||||
|
||||
expect(disjointSet.find('A')).toBe('B');
|
||||
});
|
||||
|
||||
it('should do basic manipulations on disjoint set with custom key extractor', () => {
|
||||
const keyExtractor = value => value.key;
|
||||
|
||||
const disjointSet = new DisjointSet(keyExtractor);
|
||||
|
||||
const itemA = { key: 'A', value: 1 };
|
||||
const itemB = { key: 'B', value: 2 };
|
||||
const itemC = { key: 'C', value: 3 };
|
||||
|
||||
expect(disjointSet.find(itemA)).toBeNull();
|
||||
expect(disjointSet.find(itemB)).toBeNull();
|
||||
|
||||
disjointSet.makeSet(itemA);
|
||||
|
||||
expect(disjointSet.find(itemA)).toBe('A');
|
||||
expect(disjointSet.find(itemB)).toBeNull();
|
||||
|
||||
disjointSet.makeSet(itemB);
|
||||
|
||||
expect(disjointSet.find(itemA)).toBe('A');
|
||||
expect(disjointSet.find(itemB)).toBe('B');
|
||||
|
||||
disjointSet.makeSet(itemC);
|
||||
|
||||
expect(disjointSet.inSameSet(itemA, itemB)).toBeFalsy();
|
||||
|
||||
disjointSet.union(itemA, itemB);
|
||||
|
||||
expect(disjointSet.find(itemA)).toBe('A');
|
||||
expect(disjointSet.find(itemB)).toBe('A');
|
||||
expect(disjointSet.inSameSet(itemA, itemB)).toBeTruthy();
|
||||
expect(disjointSet.inSameSet(itemB, itemA)).toBeTruthy();
|
||||
expect(disjointSet.inSameSet(itemA, itemC)).toBeFalsy();
|
||||
|
||||
disjointSet.union(itemA, itemC);
|
||||
|
||||
expect(disjointSet.find(itemA)).toBe('A');
|
||||
expect(disjointSet.find(itemB)).toBe('A');
|
||||
expect(disjointSet.find(itemC)).toBe('A');
|
||||
|
||||
expect(disjointSet.inSameSet(itemA, itemB)).toBeTruthy();
|
||||
expect(disjointSet.inSameSet(itemB, itemC)).toBeTruthy();
|
||||
expect(disjointSet.inSameSet(itemA, itemC)).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,115 @@
|
||||
import DisjointSetItem from '../DisjointSetItem';
|
||||
|
||||
describe('DisjointSetItem', () => {
|
||||
it('should do basic manipulation with disjoint set item', () => {
|
||||
const itemA = new DisjointSetItem('A');
|
||||
const itemB = new DisjointSetItem('B');
|
||||
const itemC = new DisjointSetItem('C');
|
||||
const itemD = new DisjointSetItem('D');
|
||||
|
||||
expect(itemA.getAncestorsCount()).toBe(0);
|
||||
expect(itemA.getChildren()).toEqual([]);
|
||||
expect(itemA.getKey()).toBe('A');
|
||||
expect(itemA.getRoot()).toEqual(itemA);
|
||||
expect(itemA.isRoot()).toBeTruthy();
|
||||
expect(itemB.isRoot()).toBeTruthy();
|
||||
|
||||
itemA.addChild(itemB);
|
||||
itemD.setParent(itemC);
|
||||
|
||||
expect(itemA.getAncestorsCount()).toBe(1);
|
||||
expect(itemC.getAncestorsCount()).toBe(1);
|
||||
|
||||
expect(itemB.getAncestorsCount()).toBe(0);
|
||||
expect(itemD.getAncestorsCount()).toBe(0);
|
||||
|
||||
expect(itemA.getChildren().length).toBe(1);
|
||||
expect(itemC.getChildren().length).toBe(1);
|
||||
|
||||
expect(itemA.getChildren()[0]).toEqual(itemB);
|
||||
expect(itemC.getChildren()[0]).toEqual(itemD);
|
||||
|
||||
expect(itemB.getChildren().length).toBe(0);
|
||||
expect(itemD.getChildren().length).toBe(0);
|
||||
|
||||
expect(itemA.getRoot()).toEqual(itemA);
|
||||
expect(itemB.getRoot()).toEqual(itemA);
|
||||
|
||||
expect(itemC.getRoot()).toEqual(itemC);
|
||||
expect(itemD.getRoot()).toEqual(itemC);
|
||||
|
||||
expect(itemA.isRoot()).toBeTruthy();
|
||||
expect(itemB.isRoot()).toBeFalsy();
|
||||
expect(itemC.isRoot()).toBeTruthy();
|
||||
expect(itemD.isRoot()).toBeFalsy();
|
||||
|
||||
itemA.addChild(itemC);
|
||||
|
||||
expect(itemA.isRoot()).toBeTruthy();
|
||||
expect(itemB.isRoot()).toBeFalsy();
|
||||
expect(itemC.isRoot()).toBeFalsy();
|
||||
expect(itemD.isRoot()).toBeFalsy();
|
||||
|
||||
expect(itemA.getAncestorsCount()).toEqual(3);
|
||||
expect(itemB.getAncestorsCount()).toEqual(0);
|
||||
expect(itemC.getAncestorsCount()).toEqual(1);
|
||||
});
|
||||
|
||||
it('should do basic manipulation with disjoint set item with custom key extractor', () => {
|
||||
const keyExtractor = (value) => {
|
||||
return value.key;
|
||||
};
|
||||
|
||||
const itemA = new DisjointSetItem({ key: 'A', value: 1 }, keyExtractor);
|
||||
const itemB = new DisjointSetItem({ key: 'B', value: 2 }, keyExtractor);
|
||||
const itemC = new DisjointSetItem({ key: 'C', value: 3 }, keyExtractor);
|
||||
const itemD = new DisjointSetItem({ key: 'D', value: 4 }, keyExtractor);
|
||||
|
||||
expect(itemA.getAncestorsCount()).toBe(0);
|
||||
expect(itemA.getChildren()).toEqual([]);
|
||||
expect(itemA.getKey()).toBe('A');
|
||||
expect(itemA.getRoot()).toEqual(itemA);
|
||||
expect(itemA.isRoot()).toBeTruthy();
|
||||
expect(itemB.isRoot()).toBeTruthy();
|
||||
|
||||
itemA.addChild(itemB);
|
||||
itemD.setParent(itemC);
|
||||
|
||||
expect(itemA.getAncestorsCount()).toBe(1);
|
||||
expect(itemC.getAncestorsCount()).toBe(1);
|
||||
|
||||
expect(itemB.getAncestorsCount()).toBe(0);
|
||||
expect(itemD.getAncestorsCount()).toBe(0);
|
||||
|
||||
expect(itemA.getChildren().length).toBe(1);
|
||||
expect(itemC.getChildren().length).toBe(1);
|
||||
|
||||
expect(itemA.getChildren()[0]).toEqual(itemB);
|
||||
expect(itemC.getChildren()[0]).toEqual(itemD);
|
||||
|
||||
expect(itemB.getChildren().length).toBe(0);
|
||||
expect(itemD.getChildren().length).toBe(0);
|
||||
|
||||
expect(itemA.getRoot()).toEqual(itemA);
|
||||
expect(itemB.getRoot()).toEqual(itemA);
|
||||
|
||||
expect(itemC.getRoot()).toEqual(itemC);
|
||||
expect(itemD.getRoot()).toEqual(itemC);
|
||||
|
||||
expect(itemA.isRoot()).toBeTruthy();
|
||||
expect(itemB.isRoot()).toBeFalsy();
|
||||
expect(itemC.isRoot()).toBeTruthy();
|
||||
expect(itemD.isRoot()).toBeFalsy();
|
||||
|
||||
itemA.addChild(itemC);
|
||||
|
||||
expect(itemA.isRoot()).toBeTruthy();
|
||||
expect(itemB.isRoot()).toBeFalsy();
|
||||
expect(itemC.isRoot()).toBeFalsy();
|
||||
expect(itemD.isRoot()).toBeFalsy();
|
||||
|
||||
expect(itemA.getAncestorsCount()).toEqual(3);
|
||||
expect(itemB.getAncestorsCount()).toEqual(0);
|
||||
expect(itemC.getAncestorsCount()).toEqual(1);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user