TrieTree介绍及其C#实现

作者:Tony Qu

 

在自然语言处理(NLP)研究中,NGram是最基本但也是最有用的一种比对方式,这里的N是需要比对的字符串的长度,而今天我介绍的TrieTree,正是和NGram密切相关的一种数据结构,有人称之为字典树。TrieTree简单的说是一种多叉树,每个节点保存一个字符,这么做的好处是当我们要做NGram比对时,只需要直接从树的根节点开始沿着某个树叉遍历下去,就能完成比对;如果没找到,停止本次遍历。这话讲得有些抽象,我们来看一个实际的例子。

假设我们现在词库里面有以下一些词:

上海市 
上海滩 
上海人 
上海公司 
北京 
北斗星 
杨柳 
杨浦区

根

如图所示:挂在根节点上的字有上、北、杨,

如果我们现在对“上海市杨浦区”这个词做3gram就有上海市、海市杨、市杨浦、杨浦区,现在我们要知道哪些词是能够被这个字典识别的,通常我们可以用NGram来做分词。有了这颗树,我们只需要依次取每个字符,从根开始进行比对,比如上海市,我们能够匹配 上->海->市,这个路径,所以匹配;比如海市杨,由于没有“海”字挂在根节点上,所以停止;市杨浦也无法匹配;最终匹配杨浦区,得到 杨->浦->区 这个路径,匹配。

最终我们可以把“上海市杨浦区”切分为 上海市|杨浦区。

尽管TrieTree要比普通字符串数组节省很多时间,但这并不是没有代价的,因为你要先根据字典构建这棵树,这个代价并不低,当然对于某个应用来说一旦TrieTree构建完成就可以重复使用,所以针对大规模比对来说,性能提升还是很客观的。

下面是TrieTree的C#实现。

   public class TrieTree

    {

        TrieNode _root = null;



        private TrieTree()

        {

            _root = new TrieNode(char.MaxValue,0);

            charCount = 0;

        }

        static TrieTree _instance = null;

        public static TrieTree GetInstance()

        {

            if (_instance == null)

            {

                _instance = new TrieTree();

            }

            return _instance;

        }

        public TrieNode Root 

        {

            get { return _root; }

        }

        public void AddWord(char ch)

        {

            TrieNode newnode=_root.AddChild(ch);

            newnode.IncreaseFrequency();

            newnode.WordEnded = true;

        }

        int charCount;

        public void AddWord(string word)

        {

            if (word.Length == 1)

            {

                AddWord(word[0]);

                charCount++;

            }

            else

            { 

                char[] chars=word.ToCharArray();

                TrieNode node = _root;

                charCount += chars.Length;

                for (int i = 0; i < chars.Length; i++)

                {

                    TrieNode newnode=node.AddChild(chars[i]);

                    newnode.IncreaseFrequency();

                    node = newnode;

                }

                node.WordEnded = true;

            }

        }

        public int GetFrequency(char ch)

        {

            TrieNode matchedNode = _root.Children.FirstOrDefault(n => n.Character == ch);

            if (matchedNode == null)

            {

                return 0;

            }

            return matchedNode.Frequency;             

        }

        public int GetFrequency(string word)

        {

            if (word.Length == 1)

            {

                return GetFrequency(word[0]); 

            }

            else

            {

                char[] chars = word.ToCharArray();

                TrieNode node = _root;

                for (int i = 0; i < chars.Length; i++)

                {

                    if (node.Children == null)

                        return 0;

                    TrieNode matchednode = node.Children.FirstOrDefault(n => n.Character == chars[i]);

                    if (matchednode == null)

                    {

                        return 0;

                    }

                    node = matchednode;

                }

                if (node.WordEnded == true)

                    return node.Frequency;

                else

                    return -1;

            }

        }

    }

这里我们使用了单例模式,因为TrieTree类似缓存,不需要重复创建。下面是TreeNode的实现:

    public class TrieNode

    {

        public TrieNode(char ch,int depth)

        {

            this.Character=ch;

            this._depth=depth;

        }





        public char Character;



        int _depth;

        public int Depth

        {

            get{return _depth;}

        }



        TrieNode _parent=null;

        public TrieNode Parent 

        {

            get { return _parent; }

            set { _parent = value; }

        }



        public bool WordEnded = false;





        HashSet<TrieNode> _children=null;

        public HashSet<TrieNode> Children

        {

            get { return _children; }

        }



        public TrieNode GetChildNode(char ch)

        {

            if (_children != null)

                return _children.FirstOrDefault(n => n.Character == ch);

            else

                return null;

        }



        public TrieNode AddChild(char ch)

        {

            TrieNode matchedNode=null;

            if (_children != null)

            {

                matchedNode = _children.FirstOrDefault(n => n.Character == ch);

            }

            if (matchedNode != null)    //found the char in the list

            {

                //matchedNode.IncreaseFrequency();

                return matchedNode;

            }

            else

            {   //not found

                TrieNode node = new TrieNode(ch, this.Depth + 1);

                node.Parent = this;

                //node.IncreaseFrequency();

                if (_children == null)

                    _children = new HashSet<TrieNode>();

                _children.Add(node);

                return node;

            }

        }



        int _frequency = 0;

        public int Frequency

        {

            get { return _frequency; }

        }



        public void IncreaseFrequency()

        {

            _frequency++;

        }



        public string GetWord()

        { 

            TrieNode tmp=this;

            string result = string.Empty;

            while(tmp.Parent!=null) //until root node

            {

                result = tmp.Character + result;

                tmp = tmp.Parent;

            }

            return result;

        }



        public override string ToString()

        {

            return Convert.ToString(this.Character);

        }

    }

最后提供一个能工作的演示代码,供大家参考,点击这里下载。

摘要: 本文给大家介绍TrieTree数据结构,这是NGram研究中必备的结构体之一。 阅读全文
posted @  2012-02-11 14:40 Tony Qu 阅读(654) |  评论 (0)  编辑
 
 
摘要: 相对于ASP.NET MVC,WCSF显得默默无闻许多,可能大部分人没有听到过这个框架,但是它确实存在,而且是pattern&practice team的大作。 阅读全文
posted @  2010-09-15 23:23 Tony Qu 阅读(2632) |  评论 (10)  编辑
 
 
 
摘要: NPOI 1.2教程的2.2.1节,2.2节开始将详细讲解单元格的一些相关功能和代码实现。 阅读全文
posted @  2009-03-27 09:01 Tony Qu 阅读(12516) |  评论 (11)  编辑
 
 
posted @  2009-03-25 09:58 Tony Qu 阅读(7781) |  评论 (5)  编辑
 
摘要: 这是NPOI 1.2教程第2章的1.2节,通过这一节你将了解什么是DocumentSummaryInformation和SummaryInformation,以及如何在NPOI中创建它们。 阅读全文
posted @  2009-03-24 07:11 Tony Qu 阅读(7137) |  评论 (6)  编辑
 
摘要: 这文是NPOI 1.2教程第2章的第一节,从创建Workbook开始一步步教你如何使用NPOI 1.2创建“纯种”Excel 97-2003的xls文件 阅读全文
posted @  2009-03-23 07:41 Tony Qu 阅读(15995) |  评论 (15)  编辑
 
摘要: NPOI开源项目的介绍,如果你正为生成Excel文件格式而烦恼,这套库会让你多一个选择。 阅读全文
posted @  2009-03-16 07:49 Tony Qu 阅读(19281) |  评论 (45)  编辑
 
 
posted @  2008-04-22 10:08 Tony Qu 阅读(1421) |  评论 (2)  编辑
 
 
 
 
摘要: 前两天尝试用ActiveSync同步联系人,没想到刚安装好ActiveSync就出问题,于是只能自己帮自己做troubleshooting,当然是有收获的,所以写篇文章和大家分享一下 阅读全文
posted @  2007-04-17 02:09 Tony Qu 阅读(41161) |  评论 (77)  编辑
 
 
 
摘要: 在网上找遍了也没有找到 运行时改变User Profile的Provider的方法,于是研究了一下,总的来说还是比较简单的,但有些技巧性 阅读全文
posted @  2006-12-26 17:12 Tony Qu 阅读(2488) |  评论 (5)  编辑
 
摘要: 微软asp.net论坛上最经典的session lost troubleshooting帖子。由于发觉这份文档一直没有人翻译,故翻译之,相信可以帮助很多朋友解决session lost的困惑。 阅读全文
posted @  2006-10-24 22:10 Tony Qu 阅读(21677) |  评论 (12)  编辑
 
 
 
posted @  2006-05-19 15:28 Tony Qu 阅读(5061) |  评论 (7)  编辑
 
摘要: User Profile作为asp.net 2.0的新特性之一,有相当的实用价值,本文是有关user profile的配置及使用教材,有任何翻译上或内容方面的问题请指出。 
由于文章较长,用了一个星期才翻译完,在此庆祝一下:D。 阅读全文
posted @  2005-12-18 08:19 Tony Qu 阅读(15282) |  评论 (27)  编辑
 
 

你可能感兴趣的:(tree)