字典树 Krains 2020-09-01

时间:2022-07-24
本文章向大家介绍字典树 Krains 2020-09-01,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

应用

  1. 搜索引擎的自动补全
  2. 拼写检查

当然还有其他的数据结构,如哈希表,使我们能够在字符串数据集中搜索单词。为什么我们还需要 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;
    }
}

经典例题

212. 单词搜索 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"]

涉及到匹配多个单词,用字典树。

将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;
    }
}