Trie树(字典树,前缀树)

Trie中文名又叫做字典树,前缀树等,因为其结构独有的特点,经常被用来统计,排序,和保存大量的字符串,经常见于搜索提示,输入法文字关联等,当输入一个值,可以自动搜索出可能的选择。当没有完全匹配的结果时,可以返回前缀最为相似的可能。
 
例如:上面的树由abcd abc abd,b等构成,红点表示可以为某个单词的尾巴,最上面的空白就是root节点(不携带值的)
常见的api有insert(将一个单词插入),search(查找是否存在某个单词),startsWith(是否是以某个单词作为前缀开头)
下面简单实现,使用search的来返回结果来告知是否是前缀或者存在此单词
class Trie {
    class TNode {
        // 孩子节点,英文的话最多26个字母,此处可以用HashMap来代替,可以处理中文场景等
        TNode[] ch = new TNode[26];
        // 是否可以为某个单词的尾巴
        boolean isEnd;
        // 当前字符内容,以下均是看使用场景,不是必须
        char v;
    }
TNode root
= new TNode(); void insert(String s) { // 每次插入都要从头开始构建trie树 TNode parent = root; char[] cs = s.toCharArray(); for (char c : cs) { if (parent.ch[c - 'a'] == null) { // 新的字符,则需要新建一个节点 parent.ch[c - 'a'] = new TNode(); parent.ch[c - 'a'].v = c; } parent = parent.ch[c - 'a']; } parent.isEnd = true; } // -1是不存在或者不存在前缀,0是存在前缀,1是存在此单词 int search(String s) { TNode parent = root; char[] cs = s.toCharArray(); for (char c : cs) { if (parent.ch[c - 'a'] == null) { // 说明不存在这个字符,也就是不存在这个单词 return -1; } // 指针指向child parent = parent.ch[c - 'a']; } return parent.isEnd ? 1 : 0; } }

接下来我们实现一个简单的输入法提示:

class Trie {
    class TNode {
        // 孩子节点,英文的话最多26个字母,此处可以用HashMap来代替,可以处理中文场景等
        TNode[] ch = new TNode[26];
        // 是否可以为某个单词的尾巴
        boolean isEnd;
        // 当前字符内容,以下均是看使用场景,不是必须
        char v;
    }

    TNode root = new TNode();

    void insert(String s) {
        // 每次插入都要从头开始构建trie树
        TNode parent = root;
        char[] cs = s.toCharArray();
        for (char c : cs) {
            if (parent.ch[c - 'a'] == null) {
                // 新的字符,则需要新建一个节点
                parent.ch[c - 'a'] = new TNode();
                parent.ch[c - 'a'].v = c;
            }
            parent = parent.ch[c - 'a'];
        }
        parent.isEnd = true;
    }

    // -1是不存在或者不存在前缀,0是存在前缀,1是存在此单词
    int search(String s) {
        TNode parent = root;
        char[] cs = s.toCharArray();
        for (char c : cs) {
            if (parent.ch[c - 'a'] == null) {
                // 说明不存在这个字符,也就是不存在这个单词
                return -1;
            }
            // 指针指向child
            parent = parent.ch[c - 'a'];
        }
        return parent.isEnd ? 1 : 0;
    }

    /**
     * 返回匹配出的搜索结果集合
     */
    List<String> input(String word) {
        TNode parent = root;
        char[] cs = word.toCharArray();
        for (char c : cs) {
            if (parent.ch[c - 'a'] == null) {
                return Collections.emptyList();
            }
            parent = parent.ch[c - 'a'];
        }
        // 现在位于最后一个字母就是parent.v
        List<String> res = new ArrayList<>();
        LinkedList<Character> ss = new LinkedList<>();
        // 找到所有的尾巴
        dfs(parent, ss, res);
        // 将word(也就是头)和尾巴拼上构建最终的结果
        List<String> res2 = new ArrayList<>();
        for (String s : res) {
            res2.add(word + s);
        }
        return res2;

    }

    /**
     * 使用dfs回溯,将所有包含此前缀的单词都保存到res中
     */
    private void dfs(TNode parent, LinkedList<Character> ss, List<String> res) {
        if (parent.isEnd) {
            StringBuilder sb = new StringBuilder();
            // 将字符构建成String
            for (Character c : ss) {
                sb.append(c);
            }
            res.add(sb.toString());
        }
        for (int i = 0; i < parent.ch.length; i++) {
            if (parent.ch[i] != null) {
                ss.add(parent.ch[i].v);
                // 回溯
                dfs(parent.ch[i], ss, res);
                ss.removeLast();
            }
        }
    }
}

注意上面的input就是我们实现的英文输入提示,他根据我们的输入匹配所以可能的结果,如下测试:

    public static void main(String[] args) {
        Trie tree = new Trie();
        String[] strs = {"app", "apple", "add"};
        // 构建词库,就是构建字典树使用insert一个一个单词插入
        for (String str : strs) {
            tree.insert(str);
        }
        System.out.println(tree.input("a"));// [add, app, apple]
        System.out.println(tree.input("ap"));// [app, apple]
        System.out.println(tree.input("app"));// [app, apple]
        System.out.println(tree.input("appl"));// [apple]
        System.out.println(tree.input("dd"));// []
    }

字典树的应用非常广泛,自己动手实现一个我们每天都会接触的输入法的提示也是有点意思!

 

 

 

 
posted @ 2022-09-23 20:52  junlancer  阅读(77)  评论(0编辑  收藏  举报