一、Trie树
Trie树,也叫字典树,是一种专门用来处理字符串匹配的树形结构,用来解决在一组字符串集合中快速查找某个字符串的问题
Trie树可以最大限度地减少无谓的字符串比较,查询效率比哈希表高
Trie树的本质就是利用字符串之间的公共前缀,将重复的前缀合并在一起
举个例子,有6个字符串,分别是:how、hi、her、hello、so、see。对这6个字符串做一下预处理,组成Trie树的结构,每次查找字符串都是在Trie树中进行匹配查找,构造出来的就是下图中的样子
Trie树的基本性质:
- 根节点不包含字符,除根节点以外每个节点只包含一个字符
- 从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串
- 每个节点的所有子节点代表的字符都不相同
构造Trie树分解过程如下图,构造过程的每一步都相当于往Trie树中插入一个字符串。当所有字符串都插入完成之后,Trie树就构造好了
当在Trie树中查找一个字符串,比如查找字符串her,将要查找的字符串分割成单个的字符h、e、r,然后从Trie树的根节点开始匹配。如下图所示,绿色的路径就是在Trie树中匹配的路径
如果要查找的是字符串he,用上面同样的方法,从根节点开始,沿着某条路径来匹配,如图所示,绿色的路径,是字符串he匹配的路径。但是,路径的最后一个节点e并不是红色的。也就是说,he是某个字符串的前缀子串,但并不能完全匹配任何字符串
构建Trie树的过程,需要扫描所有的字符串,时间复杂度是 O ( n ) O(n) O(n)(n表示所有字符串的长度和)。每次查询时,如果要查询的字符串长度是k,那只需要比对大约k个节点,就能完成查询操作,时间复杂度为 O ( k ) O(k) O(k)(k表示要查找的字符串的长度)
二、LeetCode208:实现Trie(前缀树)
Trie树的结点结构:
Trie树是一个有根的树,其结点具有以下字段:
- 最多R个指向子结点的链接,其中每个链接对应字母表数据集中的一个字母(此题中R为26)
- 布尔字段,以指定节点是对应键的结尾还是只是键前缀
Trie树的存储:
单词leet在Trie树中的表示:
题解:
class Trie {
class TrieNode {
char data;
TrieNode[] children = new TrieNode[26];
boolean isEnd = false;
TrieNode(char data) {
this.data = data;
}
boolean hasNode(char node) {
return children[node - 'a'] != null;
}
TrieNode findNode(char node) {
return children[node - 'a'];
}
TrieNode createNode(char node) {
children[node - 'a'] = new TrieNode(node);
return children[node - 'a'];
}
}
TrieNode head;
public Trie() {
head = new TrieNode('-');
}
public void insert(String word) {
TrieNode index = head;
for (char c : word.toCharArray()) {
if (index.hasNode(c))
index = index.findNode(c);
else
index = index.createNode(c);
}
index.isEnd = true;
}
public boolean search(String word) {
TrieNode index = head;
for (char c : word.toCharArray()) {
if (index.hasNode(c))
index = index.findNode(c);
else
return false;
}
return index.isEnd;
}
public boolean startsWith(String prefix) {
TrieNode index = head;
for (char c : prefix.toCharArray()) {
if (index.hasNode(c))
index = index.findNode(c);
else return false;
}
return true;
}
}
三、LeetCode212:单词搜索 II
给定一个二维网格board和一个字典中的单词列表words,找出所有同时在二维网格和字典中出现的单词
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用
示例:
输入:
words = ["oath","pea","eat","rain"] and board =
[
['o','a','a','n'],
['e','t','a','e'],
['i','h','k','r'],
['i','f','l','v']
]
输出: ["eat","oath"]
题解:
public List<String> findWords(char[][] board, String[] words) {
Trie trie = new Trie();
for (String word : words) {
trie.insert(word);
}
int m = board.length;
int n = board[0].length;
boolean[][] visited = new boolean[m][n];
Set<String> resultSet = new HashSet<>();
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
helper(board, visited, trie.root, i, j, m, n, resultSet);
}
}
return new ArrayList<>(resultSet);
}
private void helper(char[][] board, boolean[][] visited, TireNode node, int i, int j, int m, int n, Set<String> resultSet) {
if (i < 0 || j < 0 || i >= m || j >= n || visited[i][j])
return;
node = node.children[board[i][j] - 'a'];
if (node == null)
return;
if (node.word != null)
resultSet.add(node.word);
visited[i][j] = true;
helper(board, visited, node, i + 1, j, m, n, resultSet);
helper(board, visited, node, i - 1, j, m, n, resultSet);
helper(board, visited, node, i, j + 1, m, n, resultSet);
helper(board, visited, node, i, j - 1, m, n, resultSet);
visited[i][j] = false;
}
class Trie {
TireNode root = new TireNode();
void insert(String word) {
TireNode node = root;
for (char c : word.toCharArray()) {
if (node.children[c - 'a'] == null)
node.children[c - 'a'] = new TireNode();
node = node.children[c - 'a'];
}
node.word = word;
}
}
class TireNode {
TireNode[] children = new TireNode[26];
String word = null;
}
常用数据结构的时间、空间复杂度:
https://www.bigocheatsheet.com/