双数组字典树Double Array Trie(上)

本文转载自:http://www.cnblogs.com/zhangchaoyang 作者:Orisun 如有侵权,请联系本人,一定修改至您满意为止。


Trie树主要应用在信息检索领域,非常高效。今天我们讲Double Array Trie,请先把Trie树忘掉,把信息检索忘掉,我们来讲一个确定有限自动机(deterministic finite automaton ,DFA)的故事。所谓“确定有限自动机”是指给定一个状态和一个变量时,它能跳转到的下一个状态也就确定下来了,同时状态是有限的。请注意这里出现两个名词,一个是“状态”,一个是“变量”,下文会举例说明这两个名词的含义。

举个例子,假设我们一共有10个汉字,每个汉字就是一个“变量”。我们为每个汉字编个序号。

 

1

2

3

4

5

6

7

8

9

10

             表1. “变量”的编号

这10个汉字一共可以构成6个词语:啊,埃及,阿胶,阿根廷,阿拉伯,阿拉伯人。         

这里的每个词以及它的任意前缀都是一个“状态”,“状态”一共有10个:啊,阿,埃,阿根,阿根廷,阿胶,阿拉,阿拉伯,阿拉伯人,埃及

我们把DFA图画出来:

        图1. DFA,同时也是Trie树

在图中每个节点代表一个“状态”,每条边代表一个“变量”,并且我们把变量的编号也标在了图中。

下面我们构造两个int数组:base和check,它们的长度始终是一样的。数组的长度定多少并没有严格的规定,反正随着词语的插入,数组肯定是要扩容的。说到数组扩容,大家可以看一下java中HashMap的扩容策略,每次扩容数组的长度都会变为2的整次幂。HashMap中有这么一个精妙的函数:

1
2
3
4
5
6
7
8
9
10
//给定一个整数,返回大于等于这个数的2的整次幂
static  int  tableSizeFor( int  cap) {
         int  n = cap -  1 ;
         n |= n >>>  1 ;
         n |= n >>>  2 ;
         n |= n >>>  4 ;
         n |= n >>>  8 ;
         n |= n >>>  16 ;
         return  (n <  0 ) ?  1  :  n +  1 ;
}

回到今天的正题,我们不妨把double array的初始长度就定得大一些。两数组元素初始值均为0。

double array的初始状态:

下标

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

base

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

check

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

state

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

把词添加到词典的过程就给base和check数组中各元素赋值的过程。下面我们层次遍历图1所示的Trie树。

step1.

第一层上取到3个“状态”:啊,阿,埃。把这3个状态按照其对应的变量的编号(查表1)放到state数组中。

下标

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

base

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

check

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

state

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

step2.

当存在状态转移时,有

1
2
check[t]=s
base [s]+c=t

其中s和t代表某个状态在数组中的下标,c代表变量的编号。

此时层次遍历来到了图1所示DFA的第二层,我们看到“阿”的子节点有“阿根”、“阿胶”、“阿拉”,已知状态“阿”的下标是2,变量“根”、“胶”、“拉”的编号依次是4、5、6,下面我们要给base[2]赋值:从小到大遍历所有的正整数,直到发现某个数正整k满足base[k+4]=base[k+5]=base[k+6]=check[k+4]=check[k+5]=check[k+6]=0。得到k=1,那么就把1赋给base[2],同时也确定了状态“阿根”、“阿胶”、“阿拉”的下标依次是k+4、k+5、k+6,即5、6、7,而且check[5]=check[6]=check[7]=2。

同理,“埃”的子节点是“埃及”,状态“埃”的下标是3,变量“及”的编号是7,此时有check[1+7]=base[1+7]=0,所以base[3]=1,状态“埃及”的下标是8,check[8]=3。

遍历完DFA的第二层后得到下表:

下标

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

base

0

1

1

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

check

0

0

0

0

2

2

2

3

0

0

0

0

0

0

0

0

0

0

0

state

 

阿根

阿胶

阿拉

埃及

 

 

 

 

 

 

 

 

 

 

 

step3.

重复step2,层次遍历完整查询树之后,得到:

下标

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

base

0

1

1

0

1

0

1

0

0

1

0

0

0

0

0

0

0

0

0

check

0

0

0

0

2

2

2

3

5

7

10

0

0

0

0

0

0

0

0

state

 

阿根

阿胶

阿拉

埃及

阿根廷

阿拉伯

阿拉伯人

 

 

 

 

 

 

 

 

step4.

最后遍历一次DFA,当某个节点已经是一个词的结尾时,按下列方法修改其base值。

1
2
3
4
if ( base [i]==0)
     base [i]=-i
else
     base [i]=- base [i]

得到:

下标

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

base

-1

1

1

0

1

-6

1

-8

-9

-1

-11

0

0

0

0

0

0

0

0

check

0

0

0

0

2

2

2

3

5

7

10

0

0

0

0

0

0

0

0

state

 

阿根

阿胶

阿拉

埃及

阿根廷

阿拉伯

阿拉伯人

 

 

 

 

 

 

 

 

double array建好之后,如果词典中又动态地添加了一个新词,比如“阿拉根”,那么“阿拉”的所有子孙节点在double array中的位置要重新分配。

 

图2. 动态添加一个词

首先,把“阿拉伯”和“阿拉伯人”对应的base、check值清0,把“阿拉伯”和“阿拉伯人”从state数组中删除掉,把“阿拉”的base值清0。

下标

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

base

-1

1

1

0

1

-6

0

-8

-9

0

0

0

0

0

0

0

0

0

0

check

0

0

0

0

2

2

2

3

5

0

0

0

0

0

0

0

0

0

0

state

 

阿根

阿胶

阿拉

埃及

阿根廷

 

 

 

 

 

 

 

 

 

 

然后,按照上面step2所述的方法把“阿拉伯”、“阿拉根”插入到double array中。变量“根”、“伯”的编号是4和9,满足base[k+4]=base[k+9]=check[k+4]=check[k+9]=0的最小的k是6,所以base[7]=6,“阿拉伯”和“阿拉根”对应的下标是10和15。同理把“阿拉伯人”插入到double array中。

下标

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

base

-1

1

1

0

1

-6

6

-8

-9

0

0

0

0

0

1

0

0

0

0

check

0

0

0

0

2

2

2

3

5

7

15

0

0

0

7

0

0

0

0

state

 

阿根

阿胶

阿拉

埃及

阿根廷

阿拉根

阿拉伯人

 

 

 

阿拉伯

 

 

 

 

最后,遍历图2所示的DFA,当某个节点已经是一个词的结尾时按照step4中的方法修改其base值。

下标

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

base

-1

1

1

0

1

-6

6

-8

-9

-10

-11

0

0

0

-1

0

0

0

0

check

0

0

0

0

2

2

2

3

5

7

15

0

0

0

7

0

0

0

0

state

 

阿根

阿胶

阿拉

埃及

阿根廷

阿拉根

阿拉伯人

 

 

 

阿拉伯

 

 

 

 

 

double array建好之后,如何查询一个词是否在词典中呢?

比如要查“阿胶及”,每个字的编号是已知的,我们画出状态转移图。

变量“阿”的编号是2,base[2]=1,变量“胶”的编号是5,base[2]+5=6,我们检查一下check[6]是否等于2。check[6]确实等于2,则继续看下一个状态转移。同时我们发现base[6]是负数,这说明“阿胶”已经是一个完整的词了。

继续看下一个状态转移,base[6]=-6,负数取其相反数,base[6]=6,变量“及”的编号是7,base[6]+7=13,我们检查一下check[13]是否等于6,发现不满足,则“阿胶及”不是一个词,甚至都是不是任意一个词的前缀。

github上一个日本人贡献了他的java版的Darts(Darts本来是一种Double Array Trie的C++实现),代码如下:

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
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
import  java.io.BufferedInputStream;
import  java.io.BufferedOutputStream;
import  java.io.DataInputStream;
import  java.io.DataOutputStream;
import  java.io.File;
import  java.io.FileInputStream;
import  java.io.FileOutputStream;
import  java.io.IOException;
import  java.util.ArrayList;
import  java.util.Collections;
import  java.util.List;
 
/**
  * DoubleArrayTrie在构建双数组的过程中也借助于一棵传统的Trie树,但这棵Trie树并没有被保存下来,
  * 如果要查找以prefix为前缀的所有词不适合用DoubleArrayTrie,应该用传统的Trie树。
  *
  * @author zhangchaoyang
  *
  */
public  class  DoubleArrayTrie {
     private  final  static  int  BUF_SIZE =  16384 ; // 2^14,java采用unicode编码表示所有字符,每个字符固定用两个字节表示。考虑到每个字节的符号位都是0,所以又可以节省两个bit
     private  final  static  int  UNIT_SIZE =  8 // size of int + int
 
     private  static  class  Node {
         int  code; // 字符的unicode编码
         int  depth; // 在Trie树中的深度
         int  left; //
         int  right; //
     };
 
     private  int  check[];
     private  int  base[];
 
     private  boolean  used[];
     private  int  size;
     private  int  allocSize; // base数组当前的长度
     private  List<String> key; // 所有的词
     private  int  keySize;
     private  int  length[];
     private  int  value[];
     private  int  progress;
     private  int  nextCheckPos;
     int  error_;
 
     // 扩充base和check数组
     private  int  resize( int  newSize) {
         int [] base2 =  new  int [newSize];
         int [] check2 =  new  int [newSize];
         boolean  used2[] =  new  boolean [newSize];
         if  (allocSize >  0 ) {
             System.arraycopy(base,  0 , base2,  0 , allocSize); // 如果allocSize超过了base2的长度,会抛出异常
             System.arraycopy(check,  0 , check2,  0 , allocSize);
             System.arraycopy(used,  0 , used2,  0 , allocSize);
         }
 
         base = base2;
         check = check2;
         used = used2;
 
         return  allocSize = newSize;
     }
 
     private  int  fetch(Node parent, List<Node> siblings) {
         if  (error_ <  0 )
             return  0 ;
 
         int  prev =  0 ;
 
         for  ( int  i = parent.left; i < parent.right; i++) {
             if  ((length !=  null  ? length[i] : key.get(i).length()) < parent.depth)
                 continue ;
 
             String tmp = key.get(i);
 
             int  cur =  0 ;
             if  ((length !=  null  ? length[i] : tmp.length()) != parent.depth)
                 cur = ( int ) tmp.charAt(parent.depth) +  1 ;
 
             if  (prev > cur) {
                 error_ = - 3 ;
                 return  0 ;
             }
 
             if  (cur != prev || siblings.size() ==  0 ) {
                 Node tmp_node =  new  Node();
                 tmp_node.depth = parent.depth +  1 ;
                 tmp_node.code = cur;
                 tmp_node.left = i;
                 if  (siblings.size() !=  0 )
                     siblings.get(siblings.size() -  1 ).right = i;
 
                 siblings.add(tmp_node);
             }
 
             prev = cur;
         }
 
         if  (siblings.size() !=  0 )
             siblings.get(siblings.size() -  1 ).right = parent.right;
 
         return  siblings.size();
     }
 
     private  int  insert(List<Node> siblings) {
         if  (error_ <  0 )
             return  0 ;
 
         int  begin =  0 ;
         int  pos = ((siblings.get( 0 ).code +  1  > nextCheckPos) ? siblings.get( 0 ).code +  1
                 : nextCheckPos) -  1 ;
         int  nonzero_num =  0 ;
         int  first =  0 ;
 
         if  (allocSize <= pos)
             resize(pos +  1 );
 
         outer:  while  ( true ) {
             pos++;
 
             if  (allocSize <= pos)
                 resize(pos +  1 );
 
             if  (check[pos] !=  0 ) {
                 nonzero_num++;
                 continue ;
             else  if  (first ==  0 ) {
                 nextCheckPos = pos;
                 first =  1 ;
             }
 
             begin = pos - siblings.get( 0 ).code;
             if  (allocSize <= (begin + siblings.get(siblings.size() -  1 ).code)) {
                 // progress can be zero
                 double  l = ( 1.05  1.0  * keySize / (progress +  1 )) ?  1.05  1.0
                         * keySize / (progress +  1 );
                 resize(( int ) (allocSize * l));
             }
 
             if  (used[begin])
                 continue ;
 
             for  ( int  i =  1 ; i < siblings.size(); i++)
                 if  (check[begin + siblings.get(i).code] !=  0 )
                     continue  outer;
 
             break ;
         }
 
         // -- Simple heuristics --
         // if the percentage of non-empty contents in check between the
         // index
         // 'next_check_pos' and 'check' is greater than some constant value
         // (e.g. 0.9),
         // new 'next_check_pos' index is written by 'check'.
         if  ( 1.0  * nonzero_num / (pos - nextCheckPos +  1 ) >=  0.95 )
             nextCheckPos = pos;
 
         used[begin] =  true ;
         size = (size > begin + siblings.get(siblings.size() -  1 ).code +  1 ) ? size
                 : begin + siblings.get(siblings.size() -  1 ).code +  1 ;
 
         for  ( int  i =  0 ; i < siblings.size(); i++)
             check[begin + siblings.get(i).code] = begin;
 
         for  ( int  i =  0 ; i < siblings.size(); i++) {
             List<Node> new_siblings =  new  ArrayList<Node>();
 
             if  (fetch(siblings.get(i), new_siblings) ==  0 ) {
                 base[begin + siblings.get(i).code] = (value !=  null ) ? (-value[siblings
                         .get(i).left] -  1 ) : (-siblings.get(i).left -  1 );
 
                 if  (value !=  null  && (-value[siblings.get(i).left] -  1 ) >=  0 ) {
                     error_ = - 2 ;
                     return  0 ;
                 }
 
                 progress++;
                 // if (progress_func_) (*progress_func_) (progress,
                 // keySize);
             else  {
                 int  h = insert(new_siblings);
                 base[begin + siblings.get(i).code] = h;
             }
         }
         return  begin;
     }
 
     public  DoubleArrayTrie() {
         check =  null ;
         base =  null ;
         used =  null ;
         size =  0 ;
         allocSize =  0 ;
         // no_delete_ = false;
         error_ =  0 ;
     }
 
     // no deconstructor
 
     // set_result omitted
     // the search methods returns (the list of) the value(s) instead
     // of (the list of) the pair(s) of value(s) and length(s)
 
     // set_array omitted
     // array omitted
 
     void  clear() {
         // if (! no_delete_)
         check =  null ;
         base =  null ;
         used =  null ;
         allocSize =  0 ;
         size =  0 ;
         // no_delete_ = false;
     }
 
     public  int  getUnitSize() {
         return  UNIT_SIZE;
     }
 
     public  int  getSize() {
         return  size;
     }
 
     public  int  getTotalSize() {
         return  size * UNIT_SIZE;
     }
 
     public  int  getNonzeroSize() {
         int  result =  0 ;
         for  ( int  i =  0 ; i < size; i++)
             if  (check[i] !=  0 )
                 result++;
         return  result;
     }
 
     public  int  build(List<String> key) {
         return  build(key,  null null , key.size());
     }
 
     public  int  build(List<String> _key,  int  _length[],  int  _value[],
             int  _keySize) {
         if  (_keySize > _key.size() || _key ==  null )
             return  0 ;
 
         // progress_func_ = progress_func;
         key = _key;
         length = _length;
         keySize = _keySize;
         value = _value;
         progress =  0 ;
 
         resize( 65536  32 );
 
         base[ 0 ] =  1 ;
         nextCheckPos =  0 ;
 
         Node root_node =  new  Node();
         root_node.left =  0 ;
         root_node.right = keySize;
         root_node.depth =  0 ;
 
         List<Node> siblings =  new  ArrayList<Node>();
         fetch(root_node, siblings);
         insert(siblings);
 
         // size += (1 << 8 * 2) + 1; // ???
         // if (size >= allocSize) resize (size);
 
         used =  null ;
         key =  null ;
 
         return  error_;
     }
 
     public  void  open(String fileName)  throws  IOException {
         File file =  new  File(fileName);
         size = ( int ) file.length() / UNIT_SIZE;
         check =  new  int [size];
         base =  new  int [size];
 
         DataInputStream is =  null ;
         try  {
             is =  new  DataInputStream( new  BufferedInputStream(
                     new  FileInputStream(file), BUF_SIZE));
             for  ( int  i =  0 ; i < size; i++) {
                 base[i] = is.readInt();
                 check[i] = is.readInt();
             }
         finally  {
             if  (is !=  null )
                 is.close();
         }
     }
 
     public  void  save(String fileName)  throws  IOException {
         DataOutputStream out =  null ;
         try  {
             out =  new  DataOutputStream( new  BufferedOutputStream(
                     new  FileOutputStream(fileName)));
             for  ( int  i =  0 ; i < size; i++) {
                 out.writeInt(base[i]);
                 out.writeInt(check[i]);
             }
             out.close();
         finally  {
             if  (out !=  null )
                 out.close();
         }
     }
 
     public  int  exactMatchSearch(String key) {
         return  exactMatchSearch(key,  0 0 0 );
     }
 
     public  int  exactMatchSearch(String key,  int  pos,  int  len,  int  nodePos) {
         if  (len <=  0 )
             len = key.length();
         if  (nodePos <=  0 )
             nodePos =  0 ;
 
         int  result = - 1 ;
 
         char [] keyChars = key.toCharArray();
 
         int  b = base[nodePos];
         int  p;
 
         for  ( int  i = pos; i < len; i++) {
             p = b + ( int ) (keyChars[i]) +  1 ;
             if  (b == check[p])
                 b = base[p];
             else
                 return  result;
         }
 
         p = b;
         int  n = base[p];
         if  (b == check[p] && n <  0 ) {
             result = -n -  1 ;
         }
         return  result;
     }
 
     public  List<Integer> commonPrefixSearch(String key) {
         return  commonPrefixSearch(key,  0 0 0 );
     }
 
     public  List<Integer> commonPrefixSearch(String key,  int  pos,  int  len,
             int  nodePos) {
         if  (len <=  0 )
             len = key.length();
         if  (nodePos <=  0 )
             nodePos =  0 ;
 
         List<Integer> result =  new  ArrayList<Integer>();
 
         char [] keyChars = key.toCharArray();
 
         int  b = base[nodePos];
         int  n;
         int  p;
 
         for  ( int  i = pos; i < len; i++) {
             p = b;
             n = base[p];
 
             if  (b == check[p] && n <  0 ) {
                 result.add(-n -  1 );
             }
 
             p = b + ( int ) (keyChars[i]) +  1 ;
             if  (b == check[p])
                 b = base[p];
             else
                 return  result;
         }
 
         p = b;
         n = base[p];
 
         if  (b == check[p] && n <  0 ) {
             result.add(-n -  1 );
         }
 
         return  result;
     }
 
     // debug
     public  void  dump() {
         for  ( int  i =  0 ; i < size; i++) {
             System.err.println( "i: "  + i +  " ["  + base[i] +  ", "  + check[i]
                     "]" );
         }
     }
}

 

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
public  class  TestDoubleArrayTrie {
 
     /**
      * 检索key的前缀命中了词典中的哪些词<br>
      * key的前缀有多个,所以有可能命中词典中的多个词
      */
     @Test
     public  void  testPrefixMatch() {
         DoubleArrayTrie adt =  new  DoubleArrayTrie();
         List<String> list =  new  ArrayList<String>();
         list.add( "阿胶" );
         list.add( "阿拉伯" );
         list.add( "阿拉伯人" );
         list.add( "埃及" );
         // 所有词必须先排序
         Collections.sort(list);
         // 构建DoubleArrayTrie
         adt.build(list);
         String key =  "阿拉伯人" ;
         // 检索key的前缀命中了词典中的哪些词
         List<Integer> rect = adt.commonPrefixSearch(key);
         for  ( int  index : rect) {
             System.out.println( "前缀  "  + list.get(index) +  " matched" );
         }
         System.out.println( "=================" );
     }
 
     /**
      * 检索key是否完全命中了词典中的某个词
      */
     @Test
     public  void  testFullMatch() {
         DoubleArrayTrie adt =  new  DoubleArrayTrie();
         List<String> list =  new  ArrayList<String>();
         list.add( "阿胶" );
         list.add( "阿拉伯" );
         list.add( "阿拉伯人" );
         list.add( "埃及" );
         // 所有词必须先排序
         Collections.sort(list);
         // 构建DoubleArrayTrie
         adt.build(list);
         String key =  "阿拉" ;
         // 检索key是否完全命中了词典中的某个词
         int  index = adt.exactMatchSearch(key);
         if  (index >=  0 ) {
             System.out.println(key +  " match "  + list.get(index));
         else  {
             System.out.println(key +  " not match any term" );
         }
         key =  "阿拉伯" ;
         index = adt.exactMatchSearch(key);
         if  (index >=  0 ) {
             System.out.println(key +  " match "  + list.get(index));
         else  {
             System.out.println(key +  " not match any term" );
         }
         key =  "阿拉伯人" ;
         index = adt.exactMatchSearch(key);
         if  (index >=  0 ) {
             System.out.println(key +  " match "  + list.get(index));
         else  {
             System.out.println(key +  " not match any term" );
         }
         System.out.println( "=================" );
     }
}

  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是使用Java代码实现数组字典树Double Array Trie)的AC自动机: ```java import java.util.*; class ACNode { public int[] children; // 子节点指针 public int parent; // 父节点指针 public boolean isWordEnd; // 是否是单词结尾 public char character; // 字符 public int failureLink; // 失败指针 public List<Integer> output; // 输出 public ACNode(int parent, char character) { children = new int[26]; Arrays.fill(children, -1); this.parent = parent; this.character = character; isWordEnd = false; failureLink = -1; output = new ArrayList<>(); } } class ACTrie { private List<ACNode> trie; public ACTrie() { trie = new ArrayList<>(); trie.add(new ACNode(-1, (char) 0)); } public void insert(String word) { int node = 0; for (char c : word.toCharArray()) { int index = c - 'a'; if (trie.get(node).children[index] == -1) { trie.add(new ACNode(node, c)); trie.get(node).children[index] = trie.size() - 1; } node = trie.get(node).children[index]; } trie.get(node).isWordEnd = true; } public void buildFailureLinks() { Queue<Integer> queue = new LinkedList<>(); // 根节点的失败指针为根节点本身 trie.get(0).failureLink = 0; // 初始化第一层节点的失败指针为根节点 for (int child : trie.get(0).children) { if (child != -1) { trie.get(child).failureLink = 0; queue.add(child); } } // 广度优先遍历构建失败指针 while (!queue.isEmpty()) { int currentNode = queue.poll(); for (int i = 0; i < 26; i++) { int child = trie.get(currentNode).children[i]; if (child != -1) { queue.add(child); int failure = trie.get(currentNode).failureLink; while (trie.get(failure).children[i] == -1 && failure != 0) { failure = trie.get(failure).failureLink; } if (trie.get(failure).children[i] != -1) { failure = trie.get(failure).children[i]; } trie.get(child).failureLink = failure; // 将失败节点的输出添加到当前节点的输出中 trie.get(child).output.addAll(trie.get(failure).output); } } } } public List<Integer> search(String text) { List<Integer> result = new ArrayList<>(); int node = 0; for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); int index = c - 'a'; while (trie.get(node).children[index] == -1 && node != 0) { node = trie.get(node).failureLink; } if (trie.get(node).children[index] != -1) { node = trie.get(node).children[index]; } result.addAll(trie.get(node).output); if (trie.get(node).isWordEnd) { result.add(i); } } return result; } } public class Main { public static void main(String[] args) { ACTrie acTrie = new ACTrie(); // 添加模式串 acTrie.insert("he"); acTrie.insert("she"); acTrie.insert("his"); acTrie.insert("hers"); // 构建失败指针 acTrie.buildFailureLinks(); // 搜索文本 String text = "ushers"; List<Integer> result = acTrie.search(text); // 输出匹配位置 for (int position : result) { System.out.println("Pattern found at index: " + (position - text.length() + 1)); } } } ``` 此代码实现了数组字典树Double Array Trie)的AC自动机,用于在给定文本中搜索多个模式串的出现位置。在示例代码中,我们添加了模式串"he"、"she"、"his"和"hers",并搜索文本"ushers",输出匹配的位置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值