【问题标题】:Where do I find a standard Trie based map implementation in Java? [closed]在哪里可以找到 Java 中基于标准 Trie 的地图实现? [关闭]
【发布时间】:2010-10-12 01:17:37
【问题描述】:

我有一个 Java 程序,它存储了大量从字符串到各种对象的映射。

现在,我的选择是依赖散列(通过 HashMap)或二进制搜索(通过 TreeMap)。我想知道在流行的优质收藏库中是否有高效且标准的基于 trie 的地图实现?

我过去写过自己的,但如果可以的话,我宁愿选择标准的东西。

快速澄清:虽然我的问题很笼统,但在当前项目中,我正在处理大量由完全限定的类名或方法签名索引的数据。因此,有许多共享前缀。

【问题讨论】:

  • 字符串是预先知道的吗?是否只需要通过字符串访问?

标签: java algorithm optimization trie


【解决方案1】:

您可能想看看Trie implementation that Limewire is contributing 到 Google Guava。

【讨论】:

【解决方案2】:

核心 Java 库中没有 trie 数据结构。

这可能是因为尝试通常用于存储字符串,而 Java 数据结构更通用,通常包含任何 Object(定义相等和哈希运算),尽管它们有时仅限于 Comparable 对象(定义订单)。尽管CharSequence 适用于字符串,但“符号序列”没有通用抽象,我想您可以对其他类型的符号使用Iterable

还有一点需要考虑:当尝试在 Java 中实现传统的 trie 时,您很快就会遇到 Java 支持 Unicode 的事实。要获得任何类型的空间效率,您必须将 trie 中的字符串限制为符号的某个子集,或者放弃将子节点存储在按符号索引的数组中的传统方法。这可能是为什么尝试被认为不够通用而无法包含在核心库中的另一个原因,并且如果您实现自己的库或使用第三方库,则需要注意这一点。

【讨论】:

  • 这个答案假设我想为字符串实现一个 trie。 trie 是一种通用 数据结构,能够保存任意序列并提供快速前缀查找。
  • @PaulDraper 这个答案并没有假设你想要什么,因为你在提出问题多年后才出现。由于这个问题是专门关于字符串的,所以这就是这个答案的重点。尽管我确实花了很多时间指出 Java trie 需要推广到任何类型的 Comparable
【解决方案3】:

Apache Commons Collections v4.0 现在支持 trie 结构。

请参阅org.apache.commons.collections4.trie package info 了解更多信息。特别是检查PatriciaTrie 类:

实施 PATRICIA Trie(检索以字母数字编码的信息的实用算法)。

PATRICIA Trie 是压缩的 Trie。 PATRICIA 不是将所有数据存储在 Trie 的边缘(并且具有空的内部节点),而是将数据存储在每个节点中。这允许非常有效的遍历、插入、删除、前驱、后继、前缀、范围和选择(对象)操作。所有操作都在 O(K) 时间内执行,其中 K 是树中最大项的位数。实际上,操作实际上需要 O(A(K)) 时间,其中 A(K) 是树中所有项目的平均位数。

【讨论】:

    【解决方案4】:

    还可以查看concurrent-trees。它们支持基数和后缀树,专为高并发环境而设计。

    【讨论】:

    • 截至 2014 年,这应该是公认的答案。看起来像是维护良好、测试良好、并发的尝试实现。
    【解决方案5】:

    我编写并发布了一个简单快速的实现here

    【讨论】:

    • 我喜欢这个,但是你的每个节点都需要 1024 字节,并且只代表一个字符。由于 Java 更改了 substring() 的语义,现在插入也需要 O(n^2) 时间。这个实现真的不是很实用。
    • @Stefan Reich,该数组空间仅用于内部节点,考虑到 Trie 树的扇出速度,该空间非常小。
    • 感谢您的回答,但我不相信。尝试可能并不总是快速扩展,事实上它们可能不会使用真实数据。您的阵列扫描内容也很慢。我们真的应该使用 Patricia Tries 来让事情变得紧凑和高效。我已经做了自己的实现,我可能很快就会在这里发布。没有难过的感觉,只是尝试优化:) 很多问候
    • 我的尝试只能快速扇出,因为冗余被分解并存储在“前缀”成员中。根据您要优化的内容,有许多不同的实现空间。就我而言,我的目标是简单而实用。
    • 啊,我确实误解了那部分代码。有太多的“对象”和铸造,我没有看到它。所以这是一个Patricia Trie。我的错。
    【解决方案6】:

    我想你需要的是org.apache.commons.collections.FastTreeMap

    【讨论】:

    • 这似乎不是 trie 实现。
    【解决方案7】:

    下面是 Trie 的基本 HashMap 实现。有些人可能会觉得这很有用...

    class Trie {
    
        HashMap<Character, HashMap> root;
    
        public Trie() {
            root = new HashMap<Character, HashMap>();
        }
    
        public void addWord(String word) {
            HashMap<Character, HashMap> node = root;
            for (int i = 0; i < word.length(); i++) {
                Character currentLetter = word.charAt(i);
                if (node.containsKey(currentLetter) == false) {
                    node.put(currentLetter, new HashMap<Character, HashMap>());
                }
                node = node.get(currentLetter);
            }
        }
    
        public boolean containsPrefix(String word) {
            HashMap<Character, HashMap> node = root;
            for (int i = 0; i < word.length(); i++) {
                Character currentLetter = word.charAt(i);
                if (node.containsKey(currentLetter)) {
                    node = node.get(currentLetter);
                } else {
                    return false;
                }
            }
            return true;
        }
    }
    

    【讨论】:

      【解决方案8】:

      【讨论】:

      【解决方案9】:

      您可以尝试Completely Java 库,它具有PatriciaTrie 实现。该 API 很小且易于上手,可在 Maven central repository 中获得。

      【讨论】:

        【解决方案10】:

        您也可以查看this TopCoder 一个(需要注册...)。

        【讨论】:

        • 我确实注册了,但该组件现在无法使用。
        【解决方案11】:

        如果您需要排序地图,那么值得尝试。 如果你不这样做,那么 hashmap 会更好。 带有字符串键的 Hashmap 可以通过标准 Java 实现进行改进: Array hash map

        【讨论】:

          【解决方案12】:

          如果您不担心引入 Scala 库,您可以使用我写的 burst trie 的这个节省空间的实现。

          https://github.com/nbauernfeind/scala-burst-trie

          【讨论】:

            【解决方案13】:

            这是我的实现,请通过:GitHub - MyTrie.java

            /* usage:
                MyTrie trie = new MyTrie();
                trie.insert("abcde");
                trie.insert("abc");
                trie.insert("sadas");
                trie.insert("abc");
                trie.insert("wqwqd");
                System.out.println(trie.contains("abc"));
                System.out.println(trie.contains("abcd"));
                System.out.println(trie.contains("abcdefg"));
                System.out.println(trie.contains("ab"));
                System.out.println(trie.getWordCount("abc"));
                System.out.println(trie.getAllDistinctWords());
            */
            
            import java.util.*;
            
            public class MyTrie {
              private class Node {
                public int[] next = new int[26];
                public int wordCount;
                public Node() {
                  for(int i=0;i<26;i++) {
                    next[i] = NULL;
                  }
                  wordCount = 0;
                }
              }
            
              private int curr;
              private Node[] nodes;
              private List<String> allDistinctWords;
              public final static int NULL = -1;
            
              public MyTrie() {
                nodes = new Node[100000];
                nodes[0] = new Node();
                curr = 1;
              }
            
              private int getIndex(char c) {
                return (int)(c - 'a');
              }
            
              private void depthSearchWord(int x, String currWord) {
                for(int i=0;i<26;i++) {
                  int p = nodes[x].next[i];
                  if(p != NULL) {
                    String word = currWord + (char)(i + 'a');
                    if(nodes[p].wordCount > 0) {
                      allDistinctWords.add(word);
                    }
                    depthSearchWord(p, word);
                  }
                }
              }
            
              public List<String> getAllDistinctWords() {
                allDistinctWords = new ArrayList<String>();
                depthSearchWord(0, "");
                return allDistinctWords;
              }
            
              public int getWordCount(String str) {
                int len = str.length();
                int p = 0;
                for(int i=0;i<len;i++) {
                  int j = getIndex(str.charAt(i));
                  if(nodes[p].next[j] == NULL) {
                    return 0;
                  }
                  p = nodes[p].next[j];
                }
                return nodes[p].wordCount;
              }
            
              public boolean contains(String str) {
                int len = str.length();
                int p = 0;
                for(int i=0;i<len;i++) {
                  int j = getIndex(str.charAt(i));
                  if(nodes[p].next[j] == NULL) {
                    return false;
                  }
                  p = nodes[p].next[j];
                }
                return nodes[p].wordCount > 0;
              }
            
              public void insert(String str) {
                int len = str.length();
                int p = 0;
                for(int i=0;i<len;i++) {
                  int j = getIndex(str.charAt(i));
                  if(nodes[p].next[j] == NULL) {
                    nodes[curr] = new Node();
                    nodes[p].next[j] = curr;
                    curr++;
                  }
                  p = nodes[p].next[j];
                }
                nodes[p].wordCount++;
              }
            }
            

            【讨论】:

              【解决方案14】:

              我刚刚尝试了自己的 Concurrent TRIE 实现,但不是基于字符,它是基于 HashCode。我们仍然可以为每个 CHAR hascode 使用这个 Map of Map。
              您可以使用代码@https://github.com/skanagavelu/TrieHashMap/blob/master/src/TrieMapPerformanceTest.java 对此进行测试 https://github.com/skanagavelu/TrieHashMap/blob/master/src/TrieMapValidationTest.java

              import java.util.concurrent.atomic.AtomicReferenceArray;
              
              public class TrieMap {
                  public static int SIZEOFEDGE = 4; 
                  public static int OSIZE = 5000;
              }
              
              abstract class Node {
                  public Node getLink(String key, int hash, int level){
                      throw new UnsupportedOperationException();
                  }
                  public Node createLink(int hash, int level, String key, String val) {
                      throw new UnsupportedOperationException();
                  }
                  public Node removeLink(String key, int hash, int level){
                      throw new UnsupportedOperationException();
                  }
              }
              
              class Vertex extends Node {
                  String key;
                  volatile String val;
                  volatile Vertex next;
              
                  public Vertex(String key, String val) {
                      this.key = key;
                      this.val = val;
                  }
              
                  @Override
                  public boolean equals(Object obj) {
                      Vertex v = (Vertex) obj;
                      return this.key.equals(v.key);
                  }
              
                  @Override
                  public int hashCode() {
                      return key.hashCode();
                  }
              
                  @Override
                  public String toString() {
                      return key +"@"+key.hashCode();
                  }
              }
              
              
              class Edge extends Node {
                  volatile AtomicReferenceArray<Node> array; //This is needed to ensure array elements are volatile
              
                  public Edge(int size) {
                      array = new AtomicReferenceArray<Node>(8);
                  }
              
              
                  @Override
                  public Node getLink(String key, int hash, int level){
                      int index = Base10ToBaseX.getBaseXValueOnAtLevel(Base10ToBaseX.Base.BASE8, hash, level);
                      Node returnVal = array.get(index);
                      for(;;) {
                          if(returnVal == null) {
                              return null;
                          }
                          else if((returnVal instanceof Vertex)) {
                              Vertex node = (Vertex) returnVal;
                              for(;node != null; node = node.next) {
                                  if(node.key.equals(key)) {  
                                      return node; 
                                  }
                              } 
                              return null;
                          } else { //instanceof Edge
                              level = level + 1;
                              index = Base10ToBaseX.getBaseXValueOnAtLevel(Base10ToBaseX.Base.BASE8, hash, level);
                              Edge e = (Edge) returnVal;
                              returnVal = e.array.get(index);
                          }
                      }
                  }
              
                  @Override
                  public Node createLink(int hash, int level, String key, String val) { //Remove size
                      for(;;) { //Repeat the work on the current node, since some other thread modified this node
                          int index =  Base10ToBaseX.getBaseXValueOnAtLevel(Base10ToBaseX.Base.BASE8, hash, level);
                          Node nodeAtIndex = array.get(index);
                          if ( nodeAtIndex == null) {  
                              Vertex newV = new Vertex(key, val);
                              boolean result = array.compareAndSet(index, null, newV);
                              if(result == Boolean.TRUE) {
                                  return newV;
                              }
                              //continue; since new node is inserted by other thread, hence repeat it.
                          } 
                          else if(nodeAtIndex instanceof Vertex) {
                              Vertex vrtexAtIndex = (Vertex) nodeAtIndex;
                              int newIndex = Base10ToBaseX.getBaseXValueOnAtLevel(Base10ToBaseX.Base.BASE8, vrtexAtIndex.hashCode(), level+1);
                              int newIndex1 = Base10ToBaseX.getBaseXValueOnAtLevel(Base10ToBaseX.Base.BASE8, hash, level+1);
                              Edge edge = new Edge(Base10ToBaseX.Base.BASE8.getLevelZeroMask()+1);
                              if(newIndex != newIndex1) {
                                  Vertex newV = new Vertex(key, val);
                                  edge.array.set(newIndex, vrtexAtIndex);
                                  edge.array.set(newIndex1, newV);
                                  boolean result = array.compareAndSet(index, vrtexAtIndex, edge); //REPLACE vertex to edge
                                  if(result == Boolean.TRUE) {
                                      return newV;
                                  }
                                 //continue; since vrtexAtIndex may be removed or changed to Edge already.
                              } else if(vrtexAtIndex.key.hashCode() == hash) {//vrtex.hash == hash) {       HERE newIndex == newIndex1
                                  synchronized (vrtexAtIndex) {   
                                      boolean result = array.compareAndSet(index, vrtexAtIndex, vrtexAtIndex); //Double check this vertex is not removed.
                                      if(result == Boolean.TRUE) {
                                          Vertex prevV = vrtexAtIndex;
                                          for(;vrtexAtIndex != null; vrtexAtIndex = vrtexAtIndex.next) {
                                              prevV = vrtexAtIndex; // prevV is used to handle when vrtexAtIndex reached NULL
                                              if(vrtexAtIndex.key.equals(key)){
                                                  vrtexAtIndex.val = val;
                                                  return vrtexAtIndex;
                                              }
                                          } 
                                          Vertex newV = new Vertex(key, val);
                                          prevV.next = newV; // Within SYNCHRONIZATION since prevV.next may be added with some other.
                                          return newV;
                                      }
                                      //Continue; vrtexAtIndex got changed
                                  }
                              } else {   //HERE newIndex == newIndex1  BUT vrtex.hash != hash
                                  edge.array.set(newIndex, vrtexAtIndex);
                                  boolean result = array.compareAndSet(index, vrtexAtIndex, edge); //REPLACE vertex to edge
                                  if(result == Boolean.TRUE) {
                                      return edge.createLink(hash, (level + 1), key, val);
                                  }
                              }
                          }               
                          else {  //instanceof Edge
                              return nodeAtIndex.createLink(hash, (level + 1), key, val);
                          }
                      }
                  }
              
              
              
              
                  @Override
                  public Node removeLink(String key, int hash, int level){
                      for(;;) {
                          int index = Base10ToBaseX.getBaseXValueOnAtLevel(Base10ToBaseX.Base.BASE8, hash, level);
                          Node returnVal = array.get(index);
                          if(returnVal == null) {
                              return null;
                          }
                          else if((returnVal instanceof Vertex)) {
                              synchronized (returnVal) {
                                  Vertex node = (Vertex) returnVal;
                                  if(node.next == null) {
                                      if(node.key.equals(key)) {
                                          boolean result = array.compareAndSet(index, node, null); 
                                          if(result == Boolean.TRUE) {
                                              return node;
                                          }
                                          continue; //Vertex may be changed to Edge
                                      }
                                      return null;  //Nothing found; This is not the same vertex we are looking for. Here hashcode is same but key is different. 
                                  } else {
                                      if(node.key.equals(key)) { //Removing the first node in the link
                                          boolean result = array.compareAndSet(index, node, node.next);
                                          if(result == Boolean.TRUE) {
                                              return node;
                                          }
                                          continue; //Vertex(node) may be changed to Edge, so try again.
                                      }
                                      Vertex prevV = node; // prevV is used to handle when vrtexAtIndex is found and to be removed from its previous
                                      node = node.next;
                                      for(;node != null; prevV = node, node = node.next) {
                                          if(node.key.equals(key)) {
                                              prevV.next = node.next; //Removing other than first node in the link
                                              return node; 
                                          }
                                      } 
                                      return null;  //Nothing found in the linked list.
                                  }
                              }
                          } else { //instanceof Edge
                              return returnVal.removeLink(key, hash, (level + 1));
                          }
                      }
                  }
              
              }
              
              
              
              class Base10ToBaseX {
                  public static enum Base {
                      /**
                       * Integer is represented in 32 bit in 32 bit machine.
                       * There we can split this integer no of bits into multiples of 1,2,4,8,16 bits
                       */
                      BASE2(1,1,32), BASE4(3,2,16), BASE8(7,3,11)/* OCTAL*/, /*BASE10(3,2),*/ 
                      BASE16(15, 4, 8){       
                          public String getFormattedValue(int val){
                              switch(val) {
                              case 10:
                                  return "A";
                              case 11:
                                  return "B";
                              case 12:
                                  return "C";
                              case 13:
                                  return "D";
                              case 14:
                                  return "E";
                              case 15:
                                  return "F";
                              default:
                                  return "" + val;
                              }
              
                          }
                      }, /*BASE32(31,5,1),*/ BASE256(255, 8, 4), /*BASE512(511,9),*/ Base65536(65535, 16, 2);
              
                      private int LEVEL_0_MASK;
                      private int LEVEL_1_ROTATION;
                      private int MAX_ROTATION;
              
                      Base(int levelZeroMask, int levelOneRotation, int maxPossibleRotation) {
                          this.LEVEL_0_MASK = levelZeroMask;
                          this.LEVEL_1_ROTATION = levelOneRotation;
                          this.MAX_ROTATION = maxPossibleRotation;
                      }
              
                      int getLevelZeroMask(){
                          return LEVEL_0_MASK;
                      }
                      int getLevelOneRotation(){
                          return LEVEL_1_ROTATION;
                      }
                      int getMaxRotation(){
                          return MAX_ROTATION;
                      }
                      String getFormattedValue(int val){
                          return "" + val;
                      }
                  }
              
                  public static int getBaseXValueOnAtLevel(Base base, int on, int level) {
                      if(level > base.getMaxRotation() || level < 1) {
                          return 0; //INVALID Input
                      }
                      int rotation = base.getLevelOneRotation();
                      int mask = base.getLevelZeroMask();
              
                      if(level > 1) {
                          rotation = (level-1) * rotation;
                          mask = mask << rotation;
                      } else {
                          rotation = 0;
                      }
                      return (on & mask) >>> rotation;
                  }
              }
              

              【讨论】: