前缀树

简介: Trie(发音类似 "try")或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。

image.png


前缀树也就是字典树,Trie树


力扣上就有这么一题让你实现前缀树,咱直接看这题:208. 实现 Trie (前缀树)

Trie(发音类似 "try")或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。


请你实现 Trie 类:


Trie() 初始化前缀树对象。


void insert(String word) 向前缀树中插入字符串 word 。


boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。


boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。


class Trie {
    public Trie() {
    }
    public void insert(String word) {
    }
    public boolean search(String word) {
    }
    public boolean startsWith(String prefix) {
    }
}


我们先来看看前缀树的结构


image.png


这里每个节点下其实是有26个分支的(当然如果不止英文字母的话可以根据自己的需求设计分支的数量,本文中的是只有英文的,也就26个分支),至于 'a'、'b'、'c' 这些字符是不需要存进节点中的,只要在节点中搞一个节点数组nexts,大小为26,那么 nexts[0]nexts[25] 就分别对应 az


节点:


static class Node {
    private boolean isEnd; //用于记录当前节点是否表示插入字符串的尾节点
    private Node[] nexts = new Node[26];
}


代码很好理解,我们直接看代码


class Trie {
    static class Node {
        private boolean isEnd;
        private Node[] nexts = new Node[26];
    }
    Node root = new Node();
    public Trie() {}
    // 对于这道题来说,题目已经明确说了 1 <= word.length, prefix.length <= 2000 了,
    // 我们就不需要 校验传入参数 word 和 prefix 的合法性了
    public void insert(String word) {
        char[] str = word.toCharArray();
        Node cur = root;
        for (char c : str) {
            int index = c - 'a';
            if (cur.nexts[index] == null) {
                cur.nexts[index] = new Node();
            }
            cur = cur.nexts[index];
        }
        cur.isEnd = true;
    }
    public boolean search(String word) {
        Node cur = goToLast(word);
        //return cur == null ? false : cur.isEnd; 可以下面这样简单点
        return cur != null && cur.isEnd;
    }
    public boolean startsWith(String prefix) {
        return goToLast(prefix) != null; // 能沿着prefix走完就必然存在该前缀
    }
    // 沿着s顺着前缀树往下走
    // 返回代表s最后一个字符的节点
    public Node goToLast(String s) {
        char[] str = s.toCharArray();
        Node cur = root;
        for (char c : str) {
            int index = c - 'a';
            // 如果沿着s还没走完就为null了,那直接返回null
            if (cur.nexts[index] == null) {
                return null;
            }
            cur = cur.nexts[index];
        }
        return cur;
    }
}


上面那种实现是一个基本的结构,当我们要实现其它一些功能(返回插入过的字符串中出现了几次 word,出现了几次 prefix,删除字符串)的时候就可以用下面这种结构了


节点:


class Node {
    int pass;  //用于记录有多少个字符串经过了该节点
    int end;   //用于记录有多少个字符串以当前节点结尾
    Node[] nexts = new Node[26];
}
复制代码

代码实现:

class Trie {
    static class Node {
        int pass;  //用于记录有多少个字符串经过了该节点
        int end;   //用于记录有多少个字符串以当前节点结尾
        Node[] nexts = new Node[26];
    }
    Node root = new Node();
    public Trie() {}
    public void insert(String word) {
        if (word == null || word.length() == 0) {
            return;
        }
        char[] str = word.toCharArray();
        Node cur = root;
        for (char c : str) {
            int index = c - 'a';
            if (cur.nexts[index] == null) {
                cur.nexts[index] = new Node();
            }
            cur = cur.nexts[index];
            cur.pass++;
        }
        cur.end++;
    }
    // 返回以前插入的word里面出现多少次当前要查的word
    public int searchPro(String word) {
        Node cur = goToLast(word);
        return cur == null ? 0 : cur.end;
    }
    // 返回以前插入的word里面出现多少次prefix前缀
    public int startsWithPro(String prefix) {
        Node cur = goToLast(prefix);
        return cur == null ? 0 : cur.pass;
    }
    // 沿着前缀树往下走
    public Node goToLast(String s) {
        if (s == null || s.length() == 0) {
            return null;
        }
        char[] str = s.toCharArray();
        Node cur = root;
        for (char c : str) {
            int index = c - 'a';
            if (cur.nexts[index] == null) {
                return null;
            }
            cur = cur.nexts[index];
        }
        return cur;
    }
    // 实现删除功能
    public void delete(String word) {
        if (searchPro(word) == 0) { // 如果都没有 word 的话删个毛啊
            return;
        }
        Node cur = root;
        char[] str = word.toCharArray();
        for (char c : str) {
            int index = c - 'a';
            // 如果要去的节点pass减完后都等于0了那就没有必要再走了
            if (--cur.nexts[index].pass == 0) {
                cur.nexts[index] = null;
                return;
            }
            cur = cur.nexts[index];
        }
        cur.end--;
    }
}


好了,今天的前缀树就到这里了,下一篇讲一个前缀树的应用:AC自动机

目录
相关文章
|
5月前
哈夫曼编码和字典树
哈夫曼编码和字典树
44 0
|
5月前
|
搜索推荐
前缀树Trie
前缀树Trie
|
5月前
|
存储 C++
leetcode-208:实现 Trie (前缀树/字典树)
leetcode-208:实现 Trie (前缀树/字典树)
46 0
|
存储 自然语言处理 算法
一种好用的树结构:Trie树
一种好用的树结构:Trie树
165 0
一种好用的树结构:Trie树
理解前缀树
理解前缀树
56 0
|
存储 机器学习/深度学习 算法
|
机器学习/深度学习 存储 自然语言处理
Trie树
Trie树
101 0
|
存储 算法 Linux
秒懂算法 | 字典树
字典树是一种基础方法,请掌握字典树的静态数组存储方法,它在后缀树、回文树、AC自动机、后缀自动机中都要用到。
147 0
|
算法 大数据
前缀树
前缀树
142 0
|
存储 自然语言处理 算法
字典树详解
字典树,顾名思义,是关于“字典”的一棵树。即:它是对于字典的一种存储方式(所以是一种数据结构而不是算法)。
163 0
字典树详解