前缀树及其实现解析

前缀树及其实现解析_第1张图片

前缀树

前缀树:又称单词查找树或键树,是一种哈希树的变种。

典型应用是用于统计和排序大量的字符串(但不仅限于字符串)

利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较。

将一组字符串数组放入前缀树中的演示

String[] str = {"abc", "bck", "abd", "ace"};

从root头节点开始,将每个字符串放入树中。

在放入第一个字符串第一个字符‘a’的时候,看头节点中有没有a的路径,如果没有,就创建;如果有,就沿着a的路径走

因此在放入字符‘b’、‘c’的时候,都创建新的路径


在放入第二个字符串的时候,依旧是从头节点开始。此时头节点没有b的路径,创建新的路径

因此在放入字符‘c’、‘k’的时候,都创建新的路径


在放入第三个字符串的时候,头节点存在a的路径,沿着a的路径向下走

在a之后的节点,存在b的路径,沿着b的路径向下走

在b之后的节点,不存在d的节点,创建新的路径

... ...

前缀树及其实现解析_第2张图片

前缀树的实现解析

前缀树的节点

这里使用的是经典的用数组表示路径,因为在前缀树的相关题目中,一般会限制字符串的范围

比如这道题目限制了字符串的范围仅在小写字母的范围之中

但当字符串的返回过大,创建数组十分浪费空间

此时可以用哈希表、有序表等表示路径

key表示当前是哪一条路,value表示下一个node节点

package trietree;

public class TrieNode {
    int pass;//记录这个节点被经过多少次
    int end;//记录这个节点是多少个字符串的结尾

    public TrieNode[] nexts;//每个节点的之后的路径


    public TrieNode() {
        pass = 0;
        end = 0;
        nexts = new TrieNode[26];//先预设每个节点后面有26条路径
        //我们先设定字符串的范围仅在26个小写字母之内
        //a对应0、b对应1、c对应2 .....
        //nexts[0] == null;  表示没有a的路径
        //nexts[0] != null;  表示有a的路径
    }

}

insert()方法

如何生成前缀树

pass和end在节点上,记录字符串的记录情况

经过一个节点,就给当前节点的pass++

当字符串遍历完成,给最后一个节点的end++ 

前缀树及其实现解析_第3张图片

根节点的pass表示加入了多少个字符串

根节点的end表示加入了多少个空字符串

如果加入一个空字符串,那么根节点的pass+1,end+1

insert部分代码

package trietree;

public class TrieTree {
    private TrieNode root;

    public TrieTree(){
        root = new TrieNode();
    }

    public void insert(String str) {
        if (str == null) {//加入空字符串时,头节点的pass++、end++
            root.pass++;
            root.end++;
            return;
        }

        char[] chs = str.toCharArray();//把字符串切分为char型数组
        TrieNode node = root;
        node.pass++;

        int index = 0;
        for (int i = 0; i < chs.length; i++) {
            index = chs[i] - 'a';//a对应0、b对应1、c对应2 ...
            if (node.nexts[index] == null) {//不存在对应的路径
                node.nexts[index] = new TrieNode();//创建一个路径 == 为nexts数组赋值 == 创建下一个新的节点
            }
            //如果存在对应的路径,复用节点,下一个节点的pass++
            node = node.nexts[index];//node向下
            node.pass++;//此时是下一个节点,下一个节点的pass++
        }
        node.end++;//遍历完成一个字符串之后,end++
    }
}

search()方法

查询一个字符串str加入过几次

沿着字符串str从头节点向下查找

查找到str的最后一个字符的时候,当时的node节点的end值就是str加入的次数

如果查到一半其中一个节点没有后续节点,那么说明没有加入过,直接返回0

    //查询一个字符串str加入过几次
    public int search(String str) {
        TrieNode node = root;//头节点
        char[] chs = str.toCharArray();
        int index = 0;

        for (int i = 0; i < chs.length; i++) {
            index = chs[i] - 'a';
            if (node.nexts[index] == null) {//如果查到一半其中一个节点没有后续节点,那么说明没有加入过,直接返回0
                return 0;
            }
            node = node.nexts[index];//node向下查找
        }
        return node.end;//查找到str的最后一个字符的时候,当时的node节点的end值就是str加入的次数
    }

prefixNumber()方法 

查询所有加入的字符串中,有几个是以pre为前缀的

沿着字符串pre从头节点向下查找

查找到pre的最后一个字符的时候,当时的node节点的pass值就是str加入的次数

如果查到一半其中一个节点没有后续节点,那么说明没有以pre为前缀的,直接返回0

    //查询所有加入的字符串中,有几个是以pre为前缀的
    public int prefixNumber(String pre) {
        TrieNode node = root;//头节点
        char[] chs = pre.toCharArray();
        int index = 0;

        for (int i = 0; i < chs.length; i++) {
            index = chs[i] - 'a';
            if (node.nexts[index] == null) {//如果查到一半其中一个节点没有后续节点,那么说明没有以pre为前缀的,直接返回0
                return 0;
            }
            node = node.nexts[index];//node向下查找
        }
        return node.end;//查找到pre的最后一个字符的时候,当时的node节点的pass值就是str加入的次数
    }

delete()方法

删除在前缀树中的字符串(怎么加的怎么删)

经过一个节点,就给当前节点的pass--

当字符串遍历完成,给最后一个节点的end--

注意当节点的pass值为0的时候,这个节点不存在,把整个节点及其后续节点全部标空

    public void delete(String str) {
        if(search(str) == 0){//先确认前缀树中是否加入过str,如果没有加入过,直接返回
            return;
        }

        if (str == null) {//删除空字符串时,头节点的pass--、end--
            root.pass--;
            root.end--;
            return;
        }

        char[] chs = str.toCharArray();
        TrieNode node = root;
        node.pass--;

        int index = 0;
        for (int i = 0; i < chs.length; i++) {
            index = chs[i] - 'a';
            //当节点的pass值为0的时候,这个节点不存在,把整个节点及其后续节点全部标空
            if (node.pass == 0) {
                node = null;//Java的JVM在标空为null之后,会自动释放内存
                return;
            }
            node = node.nexts[index];//node向下
            node.pass--;//下一个节点的pass--
        }
        node.end--;//遍历完成一个字符串之后,end--
    }

你可能感兴趣的:(左神算法与数据结构,java,前端,javascript)