应用
当然还有其他的数据结构,如哈希表,使我们能够在字符串数据集中搜索单词。为什么我们还需要 Trie 树呢?尽管哈希表可以在 O(1)
时间内寻找键值,却无法高效的完成以下操作: 找到具有同一前缀的全部键值。
Trie 树优于哈希表的另一个理由是,随着哈希表大小增加,会出现大量的冲突,时间复杂度可能增加到 O(n)
,其中 n
是插入的键的数量。与哈希表相比,Trie 树在存储多个具有相同前缀的键时可以使用较少的空间, 查找键值Trie 树只需要 O(m)
的时间复杂度,其中 m
为键长。
// 字典树数据结构,isEnd标记当前结点是否为一个单词的末尾,即表示该路径下是不是一个完整的单词
// 用map存储下一个字符和其对应的结点,字典树的根不表示任何字符
class TireNode {
private boolean isEnd = false;
private Map<Character, TireNode> subNode = new HashMap<>();
public boolean isEnd() {
return isEnd;
}
public void setEnd() {
isEnd = true;
}
public TireNode getChild(Character c) {
return subNode.get(c);
}
public void addChild(Character c, TireNode node) {
subNode.put(c, node);
}
}
一些常用的字典树有关方法
class Trie{
private TireNode root = new TireNode();
// 没有处理word为空串或者为空字符""的情况
// Insert a word into the tire.
public void insert(String word) {
TireNode temp = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (temp.getChild(c) == null) {
temp.addChild(c, new TireNode());
}
temp = temp.getChild(c);
// 标记叶子结点
if (i == word.length() - 1)
temp.setEnd();
}
}
/** Returns if the word is in the trie. */
public boolean search(String word) {
TireNode temp = root;
for(int i = 0; i < word.length(); i++){
char c = word.charAt(i);
temp = temp.getChild(c);
if(temp == null){
return false;
}
}
return temp.isEnd();
}
/** Returns if there is any word in the trie that starts with the given prefix. */
public boolean startsWith(String prefix) {
TireNode temp = root;
for(int i = 0; i < prefix.length(); i++){
char c = prefix.charAt(i);
temp = temp.getChild(c);
if(temp == null){
return false;
}
}
return true;
}
}
给定一个二维网格 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"]
涉及到匹配多个单词,用字典树。
将words存入字典树,采用回溯算法遍历字典树匹配所有可能出现的单词。
class Solution {
TireNode root = new TireNode();
public void insert(String word) {
TireNode temp = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (temp.getChild(c) == null) {
temp.addChild(c, new TireNode());
}
temp = temp.getChild(c);
// 标记叶子结点
if (i == word.length() - 1)
temp.setEnd();
}
}
List<String> ans = new ArrayList<>();
Set<String> set = new HashSet<>();
int[][] d = new int[][]{{1,0},{-1,0},{0,1},{0,-1}};
int n;
int m;
public List<String> findWords(char[][] board, String[] words) {
for(String word : words){
insert(word);
}
m = board.length;
n = m == 0 ? 0 : board[0].length;
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
dfs(board, root.getChild(board[i][j]), i, j, new StringBuilder(), new boolean[m][n]);
}
}
for(String word : set){
ans.add(word);
}
return ans;
}
// 回溯查找单词
public void dfs(char[][] board, TireNode temp, int x, int y,StringBuilder sb, boolean[][] visited){
// 该单词不在字典树,返回
if(temp == null)
return ;
sb.append(board[x][y]);
visited[x][y] = true;
// 完整的一个单词在字典树,加入集合,并继续向下搜索
if(temp.isEnd()){
set.add(sb.toString());
}
for(int i = 0; i < 4; i++){
int tx = x + d[i][0];
int ty = y + d[i][1];
if(tx >= 0 && tx < m && ty >= 0 && ty < n && visited[tx][ty] == false){
dfs(board, temp.getChild(board[tx][ty]), tx, ty, sb, visited);
}
}
// 回溯
sb.delete(sb.length()-1, sb.length());
visited[x][y] = false;
}
}