0
点赞
收藏
分享

微信扫一扫

前缀树,查找树,trie数据结构:建前缀树,查字符串,查前缀,删除前缀树中的字符串

前缀树,查找树,trie数据结构:建前缀树,查字符串,查前缀,删除前缀树中的字符串

提示:前缀树是非常重要的数据结构,经常在大厂面试题中考,一考就很难!


文章目录

前缀树就是查找树

所谓前缀树,就是很多字符串【字符串数组】,他们的前缀都一样,这样我们可以新建一个数据结构Trie存起来
把字符串按照链表,挂在相同的字符路径上,也就是前缀一样的放一条路,复用一下字符。
即:那些前缀相同的,就可以复用一条路,不必存那么多相同的字符串,节省空间

一路路径是一个链表,由于一节节点,有多条分支【这里是最多26条分支代表a–z字符的走向】
多条分支就是树!【2条就是二叉树】

而且,每次我们查找前缀,或查找字符串时,也可以快速根据前缀树查到——既然是查,也叫查找树
但是前缀树用得多

比如:字符串数组arr=abc,ab,bc,kst,bc,a
咱如果用数组存,还真的把每一个字符串都存了,浪费很多空间
但是前缀树,利用链表,复用相同的前缀,就可以节约空间,方便查找。
在这里插入图片描述
前缀树的链表中,它的节点,有2个属性,p和e代表啥呢?
(1)p即pass,也就是当前节点,有多少个字符串经过了当前字符,比如a,由于有a,abc,ab经过,所以a有仨,p=3
所以
(2)e即end,也就是当前节点代表的字符,有多少个字符串以它结尾,比如图中bc的c,其e=2是因为2个bc的存在,所以e=2
(3)前缀树,还有一个属性,那就是多分支路径nexts,它是一个26长度的字符数组,代表a–z的每一条分支
啥意思呢?
0 1 2 …… 25每一个位置代表此路径是
a b c…… z字符

public static class Node{
        public int pass;
        public int end;
        public Node[] nexts;//有一个自身类型的节点数组,nexts里面放的是node

        public Node(){
            //一旦生成自己就要初始化,不然没法计数
            pass = 0;
            end = 0;
            nexts = new Node[26];//初始化啥都没有
        }
    }

前缀树是一个新的数据结构:Trie
在前缀树中,咱们它就一个节点root,今后所有的字符串都挂接在这上面

//前缀树
    public static class Trie{
        //前缀树的私有信息就是一个root,root是一个三属性节点
        private Node root;
        public Trie(){
            root = new Node();//初始root节点
        }

今后这个前缀树,可以快速地查找和使用,我们需要打牢这个基础


建树,插入word

给你一个字符串word,咱需要把它给加到已有的前缀树root上
//add(word)建前缀树,一个一个字符串加
public void add(String word)
来了一个字符串word
将其转化为字符数组str
cur从root开始挂,必然root.pass++
然后咱就找cur的path=str[i]-'a’分支【path=0—15代表cur是a–z字符分支】
原来没有这路,就新建一个节点,代表有了这个字符分支的路
然后cur.pass++
在这里插入图片描述
比如添加abc
则:root下属nexts[0]有效,代表挂a,pass=1
cur=a,cur下属nexts[1]有效,代表挂b,pass=1
cur=c,cur下属nexts[2]有效,代表挂c,pass=1,且此时abc已经结束,挂c的cur.end=1,代表abc挂好了,以c结尾的字符串有1个。
在这里插入图片描述

查找前缀树中有几个字符串word

//search(word)
        //查的就是word末尾的end
        public int search(String word)

有了前面建好的前缀树root
每次从root开始查找
比如目前前缀树root上有这些字符串:

String s1 = "abc";
String s2 = "abd";
String s3 = "abe";

请你查找s3 = "abe" 有几个?
在这里插入图片描述
咱们每次沿途找,a,b,e,只要cur下属nexts[path]有就行【path=str[i]-'a’分支【path=0—15代表cur是a–z字符分支】】
一旦查到abe,看e的end,就是几个。返回。
如果没,提前终止,返回0个
比如:查abf有几个啊?
在这里插入图片描述

查找前缀树种有几个字符串以word为前缀

//prefixSearch(word)
        //以word为前缀,也就是说word最后一个字符,cur的pass是多少,就有多少个经过这里
        public int prefixSearch(String word)

有了search的经验,咱只需要查word最后一个字符的pass,就是途径了word的字符串有几个,也就是word为前缀的有即可
比如:查ab前缀有几个啊?
很显然,3个
如果一条分支,查不到路径,提前终止,返回0
在这里插入图片描述

删除前缀树中的一个字符串word

//delete(word)
        //删除一个字符串,沿途root,cur的pass全部干掉一遍,如过提前发现某一只已经pass=0,则整条路废了
        public void delete(String word)

有了search,删除也就好办了
删除前,查一下word有吗,如果结果为0,不用删了
否则的话
(1)root.pass–;root必定少了一个,因为要删除word呗
(2)cur沿着root的word路径下去,如果发现哪条分支
–cur.nexts[path].pass==0了:提前让下属之路的pass-1,如果pass竟然等于零,说明就这一条路,就一个串。
也就是整条路,整个链表的节点的pass提前减为0了,那整条链表都得null,提前返回。
下面不用遍历了,因为前面头断了,后面尾就消失了。
在这里插入图片描述
比如,上面这个abe,就一条路,提前就over了

(3)上面的条件没进去,则说明pass>=2,所以只需要沿途下去就行,注意,pass在(2)中–了,不要重复干
然后最后end那–end即可,说明word已经被删除了
在这里插入图片描述
比如上面这个,有abe,也有abek,那你删除abe,只需要动e,其pass–,end–即可,代表abe废了,但是ebek还在的。

手撕前缀树的代码

//复习前缀树:
    public static class TrieReview{
        public Node root;

        public TrieReview(){
            root = new Node();
        }

        //add(word)建前缀树,一个一个字符串加
        public void add(String word){
            if (word.compareTo("") == 0) return;

            char[] str = word.toCharArray();//转化为字符数组去一个个找路径,加
            //没有新建一个点,代表这个位置的字符存在0--25==a--z
            Node cur = root;//挂在root上的
            cur.pass++;//新加入,必然root有一个字符串经过
            int path = 0;//索引每一个字符的方向
            for (int i = 0; i < str.length; i++) {
                path = str[i] - 'a';//0--25==a--z
                if (cur.nexts[path] == null) cur.nexts[path] = new Node();//代表存在了
                cur = cur.nexts[path];//然去这个分支,代表加入了str[i]字符了
                cur.pass++;//统计,当前cur字符来一个新的
            }
            //全部加完之后,代表多了一个str
            cur.end++;
        }

        //search(word)
        //查的就是word末尾的end
        public int search(String word){
            if (word.compareTo("") == 0) return 0;
            Node cur = root;//自然是从root开始查
            int path = 0;
            char[] str = word.toCharArray();
            for (int i = 0; i < str.length; i++) {
                path = str[i] - 'a';
                if (cur.nexts[path] == null) return 0;//压根这条路就没有
                cur = cur.nexts[path];
            }
            //查完返回end
            return cur.end;
        }

        //prefixSearch(word)
        //以word为前缀,也就是说word最后一个字符,cur的pass是多少,就有多少个经过这里
        public int prefixSearch(String word){
            if (word.compareTo("") == 0) return 0;
            char[] str = word.toCharArray();
            int path = 0;
            Node cur = root;
            for (int i = 0; i < str.length; i++) {
                path = str[i] - 'a';
                if (cur.nexts[path] == null) return 0;//压根这条路没有
                cur = cur.nexts[path];//沿着路径找
            }
            //前缀玩车个,返回pass
            return cur.pass;
        }

        //delete(word)
        //删除一个字符串,沿途root,cur的pass全部干掉一遍,如过提前发现某一只已经pass=0,则整条路废了
        public void delete(String word){
            if (word.compareTo("") == 0) return;
            //先查,暂时有没有这个字符串,有才需要删除,否则不必
            if (search(word) == 0) return;

            char[] str = word.toCharArray();
            Node cur = root;
            root.pass--;//root必然损失1个字符串
            int path = 0;
            for (int i = 0; i < str.length; i++) {
                path = str[i] - 'a';
                if (--cur.nexts[path].pass == 0) {//减过了pass待会不要多减了
                    //唯独,就一个,则提前删除返回
                    cur.nexts[path] = null;
                    return;
                }
                //这条路如果至少有2个以上,只需要沿途干掉pass,最后干掉end--
                cur = cur.nexts[path];
            }
            //最后必然干end
            cur.end--;
        }
    }

    //测试一波:
    public static void test2(){
        TrieReview trie = new TrieReview();
        String s1 = "abc";
        String s2 = "abd";
        String s3 = "abe";
        trie.add(s1);
        trie.add(s2);
        trie.add(s3);
        System.out.println(trie.search(s3));//1
        System.out.println(trie.prefixSearch("ab"));//3

        System.out.println("--------------");
        trie.delete(s3);
        System.out.println(trie.search(s3));
        System.out.println(trie.prefixSearch("ab"));
    }

    public static void main(String[] args) {
//        test();
        test2();
    }

测试一波:

1
3
--------------
0
2

建树成功
查s3有1个
ab前缀有3个
——————
删除s3,之后,s3就0个
ab前缀也只有2个了


总结

提示:重要经验:

1)前缀树=查找树,是重复利用同样前缀的多叉树,节点有pass和end,控制pass和end含义就知道哪些字符有多少个
2)未来大厂面试题中,很多都是前缀树解决的,但是遇到前缀树的题目,很难,多见,多总结把

举报

相关推荐

0 条评论