基于双数组trie树的中文分词程序

 由于前面写的朴素bayes分类器,针对英文文本进行统计分析的,现在要想用于中文文本,则需要对中文文本进行分词。找了好几个分词系统,比如张华平老师的ICTCLAS、吕震宇老师用c#改写的ICTCLAS版本、KTDictSeg分词系统V1.3.01和清华王小飞写的双数组trie树中文分词程序。这里,比较后(衡量指标主要是速度和大小),我选择了双数组trie树的中文分词程序,并把它改成了c#版本。

  CoreDict.txt是核心的词库,要用程序把它生成双数组格式词典,则需先生成。若未生成双数组格式词典则需要生成双数组格式的词典,若已有双数组格式的词典则直接加载词典即可。

  至于双数组trie树的理论,可以在网上搜索看。这里我提供两篇论文:基于双数组Trie树中文分词研究和双数组Trie树算法优化及其应用研究。

  我的改进的地方:改错的地方主要在program.cs里,而改进性能(时间)主要在Segment.cs里。其实原作者写得很粗糙,如

 // String temp = gbk.GetString(gbk.GetBytes(m_lpBaseAddress), pIter, gbk.GetByteCount(m_lpBaseAddress) – pIter);
 String temp = gbk.GetString(bytes, pIter, 2);//下一个最多为2个字节, modified by kosko

很明显,不需要每次循环都取出gbk.GetBytes(m_lpBaseAddress),我直接在循环外取出了bytes,这节省了约0.4s的时间;其次,我把每次取出内容的长度 由 (gbk.GetByteCount(m_lpBaseAddress) – pIter)改为2,因为每次取一个字符,明显,如果是汉字,便是双字节,需2,如果是英文或者字符集(如\n、\r)则只需1个字节。故我设置为每次取出长度为2,这也节省了约0.4s的时间。

  然后,我还改进了字符串追加操作m_cSegTxt.Append(gbk.GetString(bytes, lpszCur, nWordLen)); 这里我把(gbk.GetBytes(m_lpBaseAddress)换成了bytes,而且把gbk.GetByteCount()换成了nWordLen,即在循环外面直接算出gbk.GetByteCount()这节省了0.25s+0.15s的时间。

  Note:gbk是Encoding类型。

  从而我得出一结论,字符串操作是非常耗费时间的,很多时候尽量避多次免取或存长字符串。当然,IO操作也非常耗费时间,只是我还没掌握里面的精华。

  核心算法还是双数组trie树,但经过我改后,原来分一篇文章需要1.37s,现在只需约0.19s。但这对于大型应用来说,效果还太差了。

 

  下面细讲下双数组trie树的算法:

  构造双数组trie(base、check):      

   从总体上来看,都如网上所说:状态s,延续字有c1,c2….cn,那么何如确定状态s的base值呢,这样确定:找一个i值使得base[i+N(c1)]、base[i+N(c2)]、base[i+N(c3)]、base[i+N(c4)]…..base[i+N(cn)]均为0,这样便可以确定base[s]=i。*

  其实,这只是说了一部分,下面我把自己看了《双数组Trie树算法优化及其应用研究》[1]这篇论文后的做法介绍下。

        N(ch)为字符ch的innercode码

        struct StateInfo {

            public int nStateSub;//当前状态的base和check数组下标,代表着一个字

            public int nNextStateCount;//所有下一状态数(当前状态延续字的个数)

            public int[] pNextID;//下一状态ID数组,其值为当前状态延续字的内码

        } ;

  step1. 把字典里的词装入数组words[],并用数组wordSize[]存取每个词的长度,初始化base、check为0

  step2.按单词里字的序数(当前字在单词里处于第i位)循环(假设一个单词最大长为30)

      step3.把所有第i个字的相关信息提取出来放入结构体数组pStateInfo。

      step4.把这个结构体数组pStateInfo按照下一状态数进行排序

      step5.按当前状态数组pStateInfo的序数(pStateInfo)进行循环(即第k个状态)

            step6.按照*处所讲规则寻找当前状态s的base值i,然后对该当前状态的所有延续字c1、c2、c3…cn进行check赋值,         check[i+N(c1)]=s,check[i+N(c2)]=s……check[i+N(cn)]=s。

            Note:step6里,进行base值赋值时要注意,如果当前状态已经是最后一个状态(即单词的最后一个字),则base值为-i,如果当前状态时一个单词的最后一个字,但是还可以有延续字组成其他词,然后base值为-s。

  通过上面的步骤,应该就可以构建好双数组trie了。

  下面再介绍查询算法

   原理很简单:

   步1  假设当前状态为s , 输入字符为c , N( c) 为序列码.

   步2  循环过程:

            t = base[ s ] + N ( c) ;

           If ( t > = DA – S IZE)

               then 根据s 和字符c 生成的hash 码hash ( s ,ch) 得到t 值.

           If ( check [ t ] = s) then

               s = t ;

          else f ail ;

          endif

 步3  若base[ t ]不为负, 重复步骤1. 否则, t为一个可结束状态。

 

   下面我打算给出如何有这种算法构造双数组trie的例子,这样看起来更清晰。还是用一般网上利用的那个例子:

    词库:啊、阿根廷、阿胶、阿拉伯人、埃及、阿拉、阿根

    现列出这些字的内码:啊-1 阿-2 埃-3 根-4 胶-5 拉-6 及-7 廷-8 伯-9 人-10

    

    开始构造

    step1.照做,check、base初始化为0

    step2. for(j=1;j<=4;j++)//因为最大字长为4

          step3.比如,当i为1时,讨论首字,有 啊、阿、埃这三个

              啊(0)、阿(5)、埃(1)

          step4.排序后,阿(5)、埃(1)、啊(0)

          step5. for(k=0;k<3;k++)//因为第1轮有三个状态

                   step6.先讨论 阿 这个状态,设i=1,即设base[阿]=1,base[i+N(根)]=base[5]==0、base[i+N(胶)]=base[6]==0、base[i+N(拉)]=base[7]==0,故base[阿]=base[2]=1是可以的,故取base[2]=1,现在设置check值,check[5]=s=2,check[6]=s=2,check[7]=s=2.

                            然后讨论k=1时,也就是 埃 这个状态,设i=1,即设base[埃]=1,base[i+N(及)]=base[8]==0,故可取base[埃]=base[3]=1.

                            最后讨k=2时,也就是 啊 这个状态,设i=1,即设base[啊]=1,而 啊 没有延续字了,所以是一个词了,故去base[啊]=base[1]=-1*i=-1,check[1]=0.

    

   开始讨论第二轮(也就是所有单词第二个字)//j=2

      根(1)、胶(0)、拉(1)、及(0) 

      排序后:根(1)、拉(1)、胶(0)、及(0)

              for (k=0;k<4;k++)

                   讨论 根(1) 这个状态 ,设i=1,即设base[根]=1, base[i+N(廷)]=base[9]==0,因此可取base[4]=1,check[9]=s=4.一直这样做下去。。。最后得到

                   base[ ] = { 0 , – 1 , 1 , 1 , – 4 , 1 , – 6 , 1 , – 8 , – 9 ,- 1} ,

                   check [ ] = { 0 ,0 ,0 ,0 ,10 ,2 ,2 ,2 ,3 ,5 ,7} .      

    查询就是个简单的事情了,照查询算法做便是了。

 

   我写的summary:

    Double Array Trie is a easy way to fulfilla segmentation program due to its easy-understood algorithm. And the segmentspeed is high compared with other methods. The segment accuracy depends on thedictionary you give, as far as I concern, it is ok. However, it cannot identifysome words due to its dependence on the dictionary,  other segmentation methods will depend oncorpus, but other methods are more complicated to fulfill.

 

http://hi.baidu.com/christole/blog/item/fdfdd496d58f2e7854fb965d.html

    原文作者:Trie树
    原文地址: https://blog.csdn.net/zhoubl668/article/details/6957859
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞