Skip to content

Commit da80941

Browse files
committed
✨ feat(@algorithm.ts/findset): support EnhancedFindset (use through )
1 parent 3f8b2d5 commit da80941

File tree

5 files changed

+311
-1
lines changed

5 files changed

+311
-1
lines changed

Diff for: packages/findset/README-zh.md

+16
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,22 @@
127127
findset.size(4) // => 1
128128
```
129129

130+
* 创建一个增强的并查集
131+
132+
```typescript
133+
import { createEnhancedFindset } from '@algorithm.ts/findset'
134+
135+
const findset = createEnhancedFindset(100)
136+
137+
findset.init(100)
138+
findset.size(1) // => 1
139+
findset.merge(1, 2)
140+
findset.size(1) // => 2
141+
findset.size(2) // => 2
142+
findset.getSetOf(1) // => Set {1, 2}
143+
findset.getSetOf(2) // => Set {1, 2}
144+
```
145+
130146

131147
## Related
132148

Diff for: packages/findset/README.md

+19
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,25 @@ amortized constant time complexity:
128128
findset.size(4) // => 1
129129
```
130130

131+
* Create an enhanced findset:
132+
133+
On the basis of ordinary findset, this enhanced version also supports to get
134+
all the nodes on a given tree (access through the root node).
135+
136+
```typescript
137+
import { createEnhancedFindset } from '@algorithm.ts/findset'
138+
139+
const findset = createEnhancedFindset(100)
140+
141+
findset.init(100)
142+
findset.size(1) // => 1
143+
findset.merge(1, 2)
144+
findset.size(1) // => 2
145+
findset.size(2) // => 2
146+
findset.getSetOf(1) // => Set {1, 2}
147+
findset.getSetOf(2) // => Set {1, 2}
148+
```
149+
131150

132151
## Related
133152

Diff for: packages/findset/__test__/findset.spec.ts

+180-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
/* eslint-disable no-param-reassign */
12
import { randomInt } from '@algorithm.ts/knuth-shuffle'
2-
import { createFindSet, createHeuristicFindSet } from '../src'
3+
import type { EnhancedFindset } from '../src'
4+
import {
5+
createEnhancedFindset,
6+
createFindSet,
7+
createHeuristicFindSet,
8+
} from '../src'
39

410
describe('createFindSet', function () {
511
const MAX_N = 1000
@@ -91,3 +97,176 @@ describe('createHeuristicFindSet', function () {
9197
expect(count).toBe(MAX_N)
9298
})
9399
})
100+
101+
describe('enhanced-findset', function () {
102+
const findset = createEnhancedFindset(10)
103+
104+
test('init', function () {
105+
findset.init(5)
106+
for (let x = 1; x <= 5; ++x) {
107+
expect(Array.from(findset.getSetOf(x)!)).toEqual([x])
108+
}
109+
for (let x = 6; x <= 10; ++x) {
110+
expect(Array.from(findset.getSetOf(x)!)).toEqual([])
111+
}
112+
113+
expect(() => findset.init(0)).toThrow(
114+
/Invalid value, expect an integer in the range of/,
115+
)
116+
expect(() => findset.init(1)).not.toThrow()
117+
expect(() => findset.init(10)).not.toThrow()
118+
expect(() => findset.init(11)).toThrow(
119+
/Invalid value, expect an integer in the range of/,
120+
)
121+
})
122+
123+
test('merge', function () {
124+
findset.init(10)
125+
findset.merge(2, 3)
126+
expect(findset.size(2)).toEqual(2)
127+
expect(findset.size(3)).toEqual(2)
128+
expect(Array.from(findset.getSetOf(2)!).sort()).toEqual([2, 3])
129+
expect(Array.from(findset.getSetOf(3)!).sort()).toEqual([2, 3])
130+
131+
findset.merge(1, 2)
132+
expect(findset.size(1)).toEqual(3)
133+
expect(findset.size(2)).toEqual(3)
134+
expect(Array.from(findset.getSetOf(1)!).sort()).toEqual([1, 2, 3])
135+
expect(Array.from(findset.getSetOf(2)!).sort()).toEqual([1, 2, 3])
136+
137+
findset.merge(1, 1)
138+
expect(findset.size(1)).toEqual(3)
139+
expect(Array.from(findset.getSetOf(1)!).sort()).toEqual([1, 2, 3])
140+
expect(Array.from(findset.getSetOf(2)!).sort()).toEqual([1, 2, 3])
141+
})
142+
})
143+
144+
describe('leetcode', function () {
145+
test('2092 Find All People With Secret', function () {
146+
const solve = createSolve()
147+
const data: Array<{ input: Parameters<typeof solve>; answer: number[] }> = [
148+
{
149+
input: [
150+
6,
151+
[
152+
[1, 2, 5],
153+
[2, 3, 8],
154+
[1, 5, 10],
155+
],
156+
1,
157+
],
158+
answer: [0, 1, 2, 3, 5],
159+
},
160+
{
161+
input: [
162+
4,
163+
[
164+
[3, 1, 3],
165+
[1, 2, 2],
166+
[0, 3, 3],
167+
],
168+
3,
169+
],
170+
answer: [0, 1, 3],
171+
},
172+
{
173+
input: [
174+
5,
175+
[
176+
[3, 4, 2],
177+
[1, 2, 1],
178+
[2, 3, 1],
179+
],
180+
1,
181+
],
182+
answer: [0, 1, 2, 3, 4],
183+
},
184+
{
185+
input: [
186+
6,
187+
[
188+
[0, 2, 1],
189+
[1, 3, 1],
190+
[4, 5, 1],
191+
],
192+
1,
193+
],
194+
answer: [0, 1, 2, 3],
195+
},
196+
]
197+
for (const kase of data) {
198+
const [N, meetings, firstPerson] = kase.input
199+
expect(solve(N, meetings, firstPerson)).toEqual(kase.answer)
200+
}
201+
202+
function createSolve(): (
203+
N: number,
204+
meetings: number[][],
205+
firstPerson: number,
206+
) => number[] {
207+
const MAX_N = 1e5 + 10
208+
const answer: Set<number> = new Set()
209+
const nodes: Set<number> = new Set()
210+
const visited: Uint8Array = new Uint8Array(MAX_N)
211+
const findset: EnhancedFindset = createEnhancedFindset(MAX_N)
212+
213+
return function findAllPeople(
214+
N: number,
215+
meetings: number[][],
216+
firstPerson: number,
217+
): number[] {
218+
const M: number = meetings.length
219+
220+
answer.clear()
221+
answer.add(1)
222+
answer.add(firstPerson + 1)
223+
224+
meetings
225+
.sort((x, y) => x[2] - y[2])
226+
.forEach(item => {
227+
item[0] += 1
228+
item[1] += 1
229+
})
230+
231+
for (let i = 0, j: number; i < M; i = j) {
232+
const t: number = meetings[i][2]
233+
for (j = i + 1; j < M; ++j) {
234+
if (meetings[j][2] !== t) break
235+
}
236+
237+
nodes.clear()
238+
for (let k = i; k < j; ++k) {
239+
const [x, y] = meetings[k]
240+
nodes.add(x)
241+
nodes.add(y)
242+
}
243+
244+
for (const x of nodes) {
245+
findset.initNode(x)
246+
visited[x] = 0
247+
}
248+
249+
for (let k = i; k < j; ++k) {
250+
const [x, y] = meetings[k]
251+
findset.merge(x, y)
252+
}
253+
254+
for (const x of nodes) {
255+
if (!answer.has(x)) continue
256+
257+
const xx: number = findset.root(x)
258+
if (visited[xx]) continue
259+
visited[xx] = 1
260+
261+
const xxSet: Set<number> = findset.getSetOf(xx)!
262+
for (const t of xxSet) answer.add(t)
263+
}
264+
}
265+
266+
return Array.from(answer)
267+
.map(x => x - 1)
268+
.sort((x, y) => x - y)
269+
}
270+
}
271+
})
272+
})

Diff for: packages/findset/src/enhanced.ts

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import type { FindSet } from './ordinary'
2+
3+
/**
4+
* Enhanced findset.
5+
*
6+
* On the basis of ordinary findset, this enhanced version also supports to get
7+
* all the nodes on a given tree (access through the root node).
8+
*
9+
* Example:
10+
*
11+
* findset = createEnhancedFindset(20)
12+
*
13+
* findset.initNode
14+
* findset.merge(2)
15+
*/
16+
export interface EnhancedFindset extends FindSet {
17+
/**
18+
*
19+
* @param x
20+
*/
21+
initNode(x: number): void
22+
/**
23+
* Size (number of the nodes) of the tree which x belongs.
24+
* @param x
25+
*/
26+
size(x: number): number
27+
/**
28+
*
29+
* @param x
30+
*/
31+
getSetOf(x: number): Readonly<Set<number>> | undefined
32+
}
33+
34+
/**
35+
* Create an enhanced findset.
36+
* @param MAX_N max nodes in the findset.
37+
* @returns
38+
*/
39+
export function createEnhancedFindset(MAX_N: number): EnhancedFindset {
40+
const pa: Uint32Array = new Uint32Array(MAX_N + 1)
41+
const sets: Array<Set<number>> = new Array(MAX_N + 1)
42+
for (let i = 0; i <= MAX_N; ++i) sets[i] = new Set()
43+
return { init, root, merge, initNode, size, getSetOf }
44+
45+
function init(N: number): void {
46+
if (N < 1 || N > MAX_N) {
47+
throw new TypeError(
48+
`Invalid value, expect an integer in the range of [1, ${MAX_N}], but got ${N}.`,
49+
)
50+
}
51+
52+
for (let x = 1; x <= N; ++x) initNode(x)
53+
}
54+
55+
function root(x: number): number {
56+
// eslint-disable-next-line no-return-assign
57+
return pa[x] === x ? x : (pa[x] = root(pa[x]))
58+
}
59+
60+
function merge(x: number, y: number): number {
61+
let xx: number = root(x)
62+
let yy: number = root(y)
63+
if (xx === yy) return xx
64+
65+
// Heuristic combination
66+
if (sets[xx].size < sets[yy].size) {
67+
const t: number = xx
68+
xx = yy
69+
yy = t
70+
}
71+
72+
// Merge two set.
73+
for (const t of sets[yy]) sets[xx].add(t)
74+
sets[yy].clear()
75+
76+
// eslint-disable-next-line no-return-assign
77+
return (pa[yy] = xx)
78+
}
79+
80+
function initNode(x: number): void {
81+
pa[x] = x
82+
sets[x].clear()
83+
sets[x].add(x)
84+
}
85+
86+
function size(x: number): number {
87+
const xx: number = root(x)
88+
return sets[xx].size
89+
}
90+
91+
function getSetOf(x: number): Readonly<Set<number>> {
92+
const xx: number = root(x)
93+
return sets[xx]
94+
}
95+
}

Diff for: packages/findset/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
export * from './enhanced'
12
export * from './heuristic'
23
export * from './ordinary'

0 commit comments

Comments
 (0)