字典树

字典树/前缀树/单词查找树/键树(Trie):

字典树

上图表示了关键字集合 {“a”, “to”, “tea”, “ted”, “ten”, “i”, “in”, “inn”} 。

基本性质:

  1. 根节点不包含字符,除根节点外的每一个子节点都包含一个字符。
  2. 从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
  3. 每个节点的所有子节点包含的字符互不相同。
  4. 从第一字符开始有连续重复的字符只占用一个节点,比如上面的to,和ten,中重复的单词t只占用了一个节点。

应用:

  1. 前缀匹配
  2. 字符串检索
  3. 词频统计
  4. 字符串排序

模版

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

class Trie {
class Node {
int num;// 有多少单词通过这个节点,即由根至该节点组成的字符串模式出现的次数
Node[] child;// 所有的子节点
boolean isEnd;// 是不是最后一个节点
char value;// 节点的值

Node() {
num = 1;
child = new Node[SIZE];
isEnd = false;
}
}

int SIZE = 26;
Node root;

/**
* 插入一个单词
*
* @param str 单词
*/
void insert(String str) {
if (root == null) {
root = new Node();
return;
}
if (str == null || str.length() == 0) {
return;
}
Node node = root;
char[] letters = str.toCharArray();// 将目标单词转换为字符数组
for (int i = 0, len = str.length(); i < len; i++) {
int pos = letters[i] - 'a';
// 如果当前节点的儿子节点中没有该字符,则构建一个TrieNode并复值该字符
if (node.child[pos] == null) {
node.child[pos] = new Node();
node.child[pos].value = letters[i];
} else {
// 如果已经存在,则将由根至该儿子节点组成的字符串模式出现的次数+1
node.child[pos].num++;
}
node = node.child[pos];
}
node.isEnd = true;
}

/**
* 计算单词前缀的数量
*
* @param prefix 前缀
* @return 单词前缀的数量
*/
int countPrefix(String prefix) {
if (prefix == null || prefix.length() == 0) {
return -1;
}
Node node = root;
char[] letters = prefix.toCharArray();
for (int i = 0, len = prefix.length(); i < len; i++) {
int pos = letters[i] - 'a';
if (node.child[pos] == null) {
return 0;
} else {
node = node.child[pos];
}
}
return node.num;
}

/**
* 打印指定前缀的单词
*
* @param prefix 前缀
* @return 单词
*/
String hasPrefix(String prefix) {
if (prefix == null || prefix.length() == 0) {
return null;
}
Node node = root;
char[] letters = prefix.toCharArray();
for (int i = 0, len = prefix.length(); i < len; i++) {
int pos = letters[i] - 'a';
if (node.child[pos] == null) {
return null;
} else {
node = node.child[pos];
}
}
preTraverse(node, prefix);
return null;
}

/**
* 遍历经过此节点的单词
*
* @param node 节点
* @param prefix 前缀
*/
void preTraverse(Node node, String prefix) {
if (!node.isEnd) {
for (Node child : node.child) {
if (child != null) {
preTraverse(child, prefix + child.value);
}
}
return;
}
System.out.println(prefix);
}

/**
* 存在完全匹配的单词
*
* @param str 单词
* @return boolean
*/
boolean has(String str) {
if (str == null || str.length() == 0) {
return false;
}
Node node = root;
char[] letters = str.toCharArray();
for (int i = 0, len = str.length(); i < len; i++) {
int pos = letters[i] - 'a';
if (node.child[pos] != null) {
node = node.child[pos];
} else {
return false;
}
}
// 走到这一步,表明可能完全匹配,可能部分匹配,如果最后一个字符节点为末端节点,则是完全匹配,否则是部分匹配
return node.isEnd;
}

/**
* 前序遍历
*
* @param node 节点
*/
void preTraverse(Node node) {
if (node != null) {
System.out.print(node.value + "-");
for (Node child : node.child) {
preTraverse(child);
}
}
}
}
Author

Zoctan

Posted on

2018-03-19

Updated on

2023-03-14

Licensed under