Trie [前缀树]
# 介绍
在计算机科学中,trie,又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。
在图示中,键标注在节点中,值标注在节点之下。每一个完整的英文单词对应一个特定的整数。Trie 可以看作是一个确定有限状态自动机(DFA),尽管边上的符号一般是隐含在分支的顺序中的。
# 应用
trie 树常用于搜索提示。如当输入一个网址,可以自动搜索出可能的选择。当没有完全匹配的搜索结果,可以返回前缀最相似的可能。
# 实现方式
trie 树实际上是一个确定有限状态自动机 (DFA),通常用转移矩阵表示。行表示状态,列表示输入字符,(行,列)位置表示转移状态。这种方式的查询效率很高,但由于稀疏的现象严重,空间利用效率很低。也可以采用压缩的存储方式即链表来表示状态转移,但由于要线性查询,会造成效率低下。
于是人们提出了下面两种结构。
- 三数组 Trie:三数组 Trie(Triple-Array Trie)结构包括三个数组:base,next 和 check.
- 二数组 Trie:二数组 Trie(Double-Array Trie)包含 base 和 check 两个数组。base 数组的每个元素表示一个 Trie 节点,即一个状态;check 数组表示某个状态的前驱状态。
# 实现
# JavaScript
class TrieNode {
constructor(char) {
this._char = char;
this._isEndOfWord = false;
this._parent = null;
this._children = new Map();
}
/**
* @public
* @return {boolean}
*/
isRoot() {
return this._char === '';
}
/**
* @public
* @return {boolean}
*/
isLeaf() {
return this._children.size === 0;
}
/**
* @public
* @returns {string}
*/
getChar() {
return this._char;
}
/**
* @internal
* @param {TrieNode} parentNode
*/
setParent(parentNode) {
this._parent = parentNode;
return this;
}
/**
* @public
* @return {TrieNode}
*/
getParent() {
return this._parent;
}
/**
* @internal
* @param {boolean} isEndOfWord
*/
setEndOfWord(isEndOfWord) {
this._isEndOfWord = isEndOfWord;
return this;
}
/**
* @public
* @return {boolean}
*/
isEndOfWord() {
return this._isEndOfWord;
}
/**
* @internal
* @param {string} char
*/
addChild(char) {
const childNode = new TrieNode(char);
childNode.setParent(this);
this._children.set(char, childNode);
return this;
}
/**
* @internal
* @param {string} char
* @return {boolean}
*/
removeChild(char) {
return this._children.delete(char);
}
/**
* @public
* @param {string} char
* @return {TrieNode}
*/
getChild(char) {
return this._children.get(char) || null;
}
/**
* @public
* @param {string} char
* @return {boolean}
*/
hasChild(char) {
return this._children.has(char);
}
/**
* @internal
* @return {Map}
*/
children() {
return this._children;
}
/**
* @public
* @return {number}
*/
childrenCount() {
return this._children.size;
}
}
class Trie {
constructor() {
this._root = new TrieNode('');
this._wordsCount = 0;
this._nodesCount = 1; // root node
}
/**
* Inserts a word into the trie
* @public
* @param {any} value
* @returns {Trie}
*/
insert(value) {
if (value === undefined || value === null) {
return this;
}
const word = value.toString();
let currentNode = this._root;
for (let i = 0; i < word.length; i += 1) {
if (!currentNode.hasChild(word[i])) {
currentNode.addChild(word[i]);
this._nodesCount += 1;
}
currentNode = currentNode.getChild(word[i]);
}
if (!currentNode.isEndOfWord()) {
currentNode.setEndOfWord(true);
this._wordsCount += 1;
}
return this;
}
/**
* Checks if a word exists in the trie
* @public
* @param {any} value
* @returns {boolean}
*/
has(value) {
if (value === undefined || value === null) {
return false;
}
const word = value.toString();
let currentNode = this._root;
for (let i = 0; i < word.length; i += 1) {
if (!currentNode.hasChild(word[i])) {
return false;
}
currentNode = currentNode.getChild(word[i]);
}
if (!currentNode.isEndOfWord()) {
return false;
}
return true;
}
/**
* Finds a word in the trie and returns its last char node
* @public
* @param {any} value
* @returns {TrieNode}
*/
find(value) {
if (value === undefined || value === null) {
return null;
}
const word = value.toString();
let currentNode = this._root;
for (let i = 0; i < word.length; i += 1) {
if (!currentNode.hasChild(word[i])) {
return null;
}
currentNode = currentNode.getChild(word[i]);
}
if (!currentNode.isEndOfWord()) {
return null;
}
return currentNode;
}
/**
* Removes a word from the trie
* @public
* @param {string} word
* @returns {string | null}
*/
remove(value) {
if (value === undefined || value === null) {
return null;
}
const word = value.toString();
let currentNode = this._root;
for (let i = 0; i < word.length; i += 1) {
if (!currentNode.hasChild(word[i])) {
return null;
}
currentNode = currentNode.getChild(word[i]);
}
if (!currentNode.isEndOfWord()) {
return null;
}
if (currentNode.childrenCount() > 0 || word === '') {
currentNode.setEndOfWord(false);
this._wordsCount -= 1;
return word;
}
do {
currentNode.getParent().removeChild(currentNode.getChar());
this._nodesCount -= 1;
currentNode = currentNode.getParent();
} while (
currentNode.isLeaf()
&& !currentNode.isEndOfWord()
&& !currentNode.isRoot()
);
this._wordsCount -= 1;
return word;
}
/**
* Traverse the trie and pass words to a callback
* @public
* @param {function} cb
*/
forEach(cb) {
if (typeof cb !== 'function') {
throw new Error('Trie.forEach expects a callback function');
}
const forEachRecursive = (node = this._root, word = '') => {
if (node.isEndOfWord()) {
cb(word);
}
node.children().forEach((child) => {
forEachRecursive(child, word + child.getChar());
});
};
return forEachRecursive();
}
/**
* Converts the trie into an array of words
* @public
* @returns {array}
*/
toArray() {
const result = [];
this.forEach((word) => result.push(word));
return result;
}
/**
* @public
* @returns {number}
*/
nodesCount() {
return this._nodesCount;
}
/**
* @public
* @returns {number}
*/
wordsCount() {
return this._wordsCount;
}
/**
* Clears the trie
* @public
*/
clear() {
this._root = new TrieNode('');
this._nodesCount = 1;
this._wordsCount = 0;
}
/**
* Converts an existing list into a trie
* @public
* @static
* @returns {Trie}
*/
static fromArray(values) {
const trie = new Trie();
values.forEach((value) => trie.insert(value));
return trie;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# 参考
编辑 (opens new window)
上次更新: 2022/10/13, 15:33:21