几种Trie树性能比较

《几种Trie树性能比较》

最近正在做一个自己的NLP库,刚起步的第一个问题就是字典的储存与查询。毫无疑问,最佳的数据结构是Trie树,同时为了平衡效率和空间,决定使用双数组Trie树。现在的问题是,双数组Trie树是一个压缩的Trie树,在插入的时候需要递归调整base与check中的冲突,无异于重新压缩一次。所以在动态的词典场景下,需要一个支持快速插入、同时查询速度还行的结构。我调查了很多开源实现,发现“首字直接分配内存,之后用动态数组二分”与“Radix and Crit Bit Tree”两种比较有实用价值。在此对两种方法做个比较,同时与标准的DoubleArrayTrie树一起比较一下速度。

二分法采用的实现是ansj的tree_split,Radix and Crit Bit Tree采用的是patricia-trie,标准的DoubleArrayTrie树采用了Jada和darts(两个库都是日本人写的),另外digitalstain的Datrie简直太糟糕了在测试中指针越界,懒得看那坨shit一样的代码。

我用一个真实的词典做为输入,连续查找100000000次一个词来比较查询速度

测试代码如下:

  1. package com.hankcs;
  2.  
  3. import darts.DoubleArrayTrie;
  4. import love.cq.domain.Forest;
  5. import love.cq.library.Library;
  6. import love.cq.splitWord.GetWord;
  7. import net.reduls.jada.Trie;
  8. import net.reduls.jada.TrieBuilder;
  9. import org.ardverk.collection.PatriciaTrie;
  10. import org.ardverk.collection.StringKeyAnalyzer;
  11. import org.digitalstain.datrie.DoubleArrayTrieImpl;
  12. import org.digitalstain.datrie.SearchResult;
  13. import org.digitalstain.datrie.store.IntegerArrayList;
  14.  
  15. import java.io.*;
  16. import java.util.ArrayList;
  17. import java.util.Collections;
  18. import java.util.List;
  19.  
  20. public class Main
  21. {
  22.     static int testTimes = 100000000;
  23.     public static void main(String[] args) throws Exception
  24.     {
  25.         List<String> wordList = new ArrayList<String>();
  26.         BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("data/WordList.txt")));
  27.         String line = null;
  28.         while ((line = br.readLine()) != null)
  29.         {
  30.             wordList.add(line);
  31.         }
  32.         br.close();
  33. //        Collections.sort(wordList);
  34. //        BufferedWriter writer = new BufferedWriter(new FileWriter("data/WordList.txt", false));
  35. //        for (String w : wordList)
  36. //        {
  37. //            writer.write(w);
  38. //            writer.newLine();
  39. //        }
  40. //        writer.close();
  41.         System.out.println("单词表大小:" + wordList.size());
  42.         long start = System.currentTimeMillis();
  43.         // 测试ansj的tire
  44.         Forest forest = new Forest();
  45.         for (String w : wordList)
  46.         {
  47.             Library.insertWord(forest, w);
  48.         }
  49.         System.out.println("ansj插入耗时:" + (System.currentTimeMillis() - start));
  50.  
  51.         start = System.currentTimeMillis();
  52.         for (int i = 0; i < testTimes; ++i)
  53.         {
  54.             if (forest.getWord("好好").getAllWords().equals("好好") == false)
  55.             {
  56.                 throw new RuntimeException("ansj没找到该有的词");
  57.             }
  58.         }
  59.         System.out.println("ansj查询耗时:" + (System.currentTimeMillis() - start));
  60.  
  61.         start = System.currentTimeMillis();
  62.         PatriciaTrie<String, String> trie = new PatriciaTrie<String, String>(StringKeyAnalyzer.CHAR);
  63.         for (String w : wordList)
  64.         {
  65.             trie.put(w, w);
  66.         }
  67.         System.out.println("PatriciaTrie插入耗时:" + (System.currentTimeMillis() - start));
  68.  
  69.         start = System.currentTimeMillis();
  70.         for (int i = 0; i < testTimes; ++i)
  71.         {
  72.             if (trie.containsKey("好好") == false)
  73.             {
  74.                 throw new RuntimeException("PatriciaTrie没找到该有的词");
  75.             }
  76.         }
  77.         System.out.println("PatriciaTrie查询耗时:" + (System.currentTimeMillis() - start));
  78.  
  79.         start = System.currentTimeMillis();
  80.         TrieBuilder builder = new TrieBuilder(wordList);
  81.         Trie trieJada = builder.build();
  82.         System.out.println("Jada插入耗时:" + (System.currentTimeMillis() - start));
  83.  
  84.         start = System.currentTimeMillis();
  85.         for (int i = 0; i < testTimes; ++i)
  86.         {
  87.             if (trieJada.search("好好") == -1)
  88.             {
  89.                 throw new RuntimeException("Jada没找到该有的词");
  90.             }
  91.         }
  92.         System.out.println("Jada查询耗时:" + (System.currentTimeMillis() - start));
  93.  
  94.         start = System.currentTimeMillis();
  95.         DoubleArrayTrie dat = new DoubleArrayTrie();
  96.         dat.build(wordList);
  97.         System.out.println("darts插入耗时:" + (System.currentTimeMillis() - start));
  98.  
  99.         start = System.currentTimeMillis();
  100.         for (int i = 0; i < testTimes; ++i)
  101.         {
  102.             if (dat.exactMatchSearch("加油") == -1)
  103.             {
  104.                 throw new RuntimeException("darts没找到该有的词");
  105.             }
  106.         }
  107.         System.out.println("darts查询耗时:" + (System.currentTimeMillis() - start));
  108.  
  109.         // digitalstainDatrie is a piece of shit!
  110. //        start = System.currentTimeMillis();
  111. //        DoubleArrayTrieImpl digitalstainDatrie = new DoubleArrayTrieImpl(wordList.size());
  112. //        digitalstainDatrie.add(wordList);
  113. //        System.out.println("digitalstainDatrie插入耗时:" + (System.currentTimeMillis() - start));
  114. //
  115. //        start = System.currentTimeMillis();
  116. //        for (int i = 0; i < testTimes; ++i)
  117. //        {
  118. //            if (digitalstainDatrie.containsPrefix(new IntegerArrayList("加油")) != SearchResult.PERFECT_MATCH)
  119. //            {
  120. //                throw new RuntimeException("digitalstainDatrie没找到该有的词");
  121. //            }
  122. //        }
  123. //        System.out.println("darts查询耗时:" + (System.currentTimeMillis() - start));
  124.     }
  125. }

输出:

  1. 单词表大小:275909
  2. ansj插入耗时:189
  3. ansj查询耗时:7756
  4. PatriciaTrie插入耗时:156
  5. PatriciaTrie查询耗时:13960
  6. Jada插入耗时:1711
  7. Jada查询耗时:3463
  8. darts插入耗时:1444
  9. darts查询耗时:1479

插入耗时

可见在20万词的词典插入中,二分法和基数树表现得非常突出,双数组Trie树因为要压缩(甚至解决冲突,如Jada)就多花了一个数量级的时间。不过这也没什么大不了的,因为分词词典就加载一次。

查询耗时

这才是重头戏,在查询中双数组Trie树表现得非常突出,二分法次之,而基数树则是二分法的两倍。

结论

二分法小巧实用。

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

发表评论

电子邮件地址不会被公开。 必填项已用*标注