伍佰目录 短网址
  当前位置:海洋目录网 » 站长资讯 » 教育考试 » 文章详细 订阅RssFeed

数据结构与算法专题——第十二题 Trie树

来源:本站原创 浏览:93次 时间:2022-12-27

今天来聊一聊Trie树,Trie树的名字有很多,比如字典树,前缀树等等。

一:概念

下面有and,as,at,cn,com这几个关键词,构建成 trie 树如下。

从上面图中,应该可以或多或少的发现一些好玩的特性。

  • 根节点不包含字符,除根节点外的每一个子节点都包含一个字符。
  • 从根节点到某一节点,路径上经过的字符连接起来,就是该节点对应的字符串。
  • 每个单词的公共前缀作为一个字符节点保存。
二:使用范围

既然学Trie树,肯定要知道这玩意是用来干嘛的?

1. 词频统计。

可能有人要说了,词频统计简单啊,一个hash或者一个堆就可以打完收工,但问题来了,如果内存有限呢?还能这么玩吗?这种限制级条件下就可以用trie树来压缩下空间,因为公共前缀都是用一个节点保存的。

2. 前缀匹配

就拿上面的图来说吧,如果我想获取所有以 "a" 开头的字符串,从图中可以很明显的看到是:and,as,at,如果不用trie树,你该怎么做呢?很显然朴素的做法时间复杂度为O(N2) ,用Trie树就不一样了,它可以做到h,h为你检索单词的长度,可以说这是秒杀的效果。

举个例子:现有一个编号为1的字符串”and“,怎样插入到trie树中呢?采用动态规划的思想,将编号”1“计入到每个途径的节点中,那么以后我们要找”a“,”an“,”and"为前缀的字符串的编号将会轻而易举。

三:实际操作

到现在为止,我想大家已经对trie树有了大概的掌握,下面看看如何来实现。

1:定义trie树节点

为了方便,我也采用纯英文字母,大家都知道字母有26个,所以构建的trie树就是一个26叉树,每个节点包含26个子节点,实现代码如下:


        /// <summary>
        /// Trie树节点
        /// </summary>
        public class TrieNode
        {
            /// <summary>
            /// 26个字符,也就是26叉树
            /// </summary>
            public TrieNode[] childNodes;

            /// <summary>
            /// 词频统计
            /// </summary>
            public int freq;

            /// <summary>
            /// 记录该节点的字符
            /// </summary>
            public char nodeChar;

            /// <summary>
            /// 插入记录时的编码id
            /// </summary>
            public HashSet<int> hashSet = new HashSet<int>();

            /// <summary>
            /// 初始化
            /// </summary>
            public TrieNode()
            {
                childNodes = new TrieNode[26];
                freq = 0;
            }
        }

2: 添加操作

既然是26叉树,那么当前节点的后续子节点是放在当前节点的哪一叉中,也就是放在childNodes中哪一个位置,这里采用 int k = word[0] - 'a'来计算位置。


       /// <summary>
       /// 插入操作
       /// </summary>
       /// <param name="root"></param>
       /// <param name="s"></param>
       public void AddTrieNode(ref TrieNode root, string word, int id)
       {
           if (word.Length == 0)
               return;

           //求字符地址,方便将该字符放入到26叉树中的哪一叉中
           int k = word[0] - 'a';

           //如果该叉树为空,则初始化
           if (root.childNodes[k] == null)
           {
               root.childNodes[k] = new TrieNode();

               //记录下字符
               root.childNodes[k].nodeChar = word[0];
           }

           //该id途径的节点
           root.childNodes[k].hashSet.Add(id);

           var nextWord = word.Substring(1);

           //说明是最后一个字符,统计该词出现的次数
           if (nextWord.Length == 0)
               root.childNodes[k].freq++;

           AddTrieNode(ref root.childNodes[k], nextWord, id);
       }

3:删除操作

删除操作中,不仅要删除该节点的字符串编号,还要对词频减一操作。


       /// <summary>
       /// 删除操作
       /// </summary>
       /// <param name="root"></param>
       /// <param name="newWord"></param>
       /// <param name="oldWord"></param>
       /// <param name="id"></param>
       public void DeleteTrieNode(ref TrieNode root, string word, int id)
       {
           if (word.Length == 0)
               return;

           //求字符地址,方便将该字符放入到26叉树种的哪一颗树中
           int k = word[0] - 'a';

           //如果该叉树为空,则说明没有找到要删除的点
           if (root.childNodes[k] == null)
               return;

           var nextWord = word.Substring(1);

           //如果是最后一个单词,则减去词频
           if (word.Length == 0 && root.childNodes[k].freq > 0)
               root.childNodes[k].freq--;

           //删除途经节点
           root.childNodes[k].hashSet.Remove(id);

           DeleteTrieNode(ref root.childNodes[k], nextWord, id);
       }

4:测试

这里我从网上下载了一套的词汇表,共2279条词汇,现在要做的就是检索 “go” 开头的词汇,并统计go出现的频率。


    public static void Main()
        {
            Trie trie = new Trie();

            var file = File.ReadAllLines(Environment.CurrentDirectory + "//1.txt");

            foreach (var item in file)
            {
                var sp = item.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

                trie.AddTrieNode(sp.LastOrDefault().ToLower(), Convert.ToInt32(sp[0]));
            }

            Stopwatch watch = Stopwatch.StartNew();

            //检索go开头的字符串
            var hashSet = trie.SearchTrie("go");

            foreach (var item in hashSet)
            {
                Console.WriteLine("当前字符串的编号ID为:{0}", item);
            }

            watch.Stop();

            Console.WriteLine("耗费时间:{0}", watch.ElapsedMilliseconds);

            Console.WriteLine("\n\ngo 出现的次数为:{0}\n\n", trie.WordCount("go"));
        }

下面我们拿着ID到txt中去找一找,嘿嘿,是不是很有意思。



  推荐站点

  • At-lib分类目录At-lib分类目录

    At-lib网站分类目录汇集全国所有高质量网站,是中国权威的中文网站分类目录,给站长提供免费网址目录提交收录和推荐最新最全的优秀网站大全是名站导航之家

    www.at-lib.cn
  • 中国链接目录中国链接目录

    中国链接目录简称链接目录,是收录优秀网站和淘宝网店的网站分类目录,为您提供优质的网址导航服务,也是网店进行收录推广,站长免费推广网站、加快百度收录、增加友情链接和网站外链的平台。

    www.cnlink.org
  • 35目录网35目录网

    35目录免费收录各类优秀网站,全力打造互动式网站目录,提供网站分类目录检索,关键字搜索功能。欢迎您向35目录推荐、提交优秀网站。

    www.35mulu.com
  • 就要爱网站目录就要爱网站目录

    就要爱网站目录,按主题和类别列出网站。所有提交的网站都经过人工审查,确保质量和无垃圾邮件的结果。

    www.912219.com
  • 伍佰目录伍佰目录

    伍佰网站目录免费收录各类优秀网站,全力打造互动式网站目录,提供网站分类目录检索,关键字搜索功能。欢迎您向伍佰目录推荐、提交优秀网站。

    www.wbwb.net