前缀树问题

时间:2011-03-31 11:27:03

标签: java algorithm

我需要管理近1,300,000个单词(某些单词组相似)。 我正在做一些小词汇,每个词都有描述。 需要快速搜索词汇。所以我决定使用前缀树。 首先需要构建预加密树(这是一个缓慢的过程,我知道),在快速浏览词汇后可能会有组织。

但是我的问题 - 前缀树的构建速度非常慢(前300,000个字很快就会建立起来,但是尾部构建非常非常缓慢,所以我不能等到树构建!!)。

这是我的前缀树类:

public class InverseVocabularyTree implements Serializable 
{
    private HashMap<Character, InverseVocabularyTree> childs;
    private String description; 

    public InverseVocabularyTree() {        
        childs=new HashMap<Character, InverseVocabularyTree>();     
    }

    public void addWord(String word, String description){       
        InverseVocabularyTree tr=this;      
        InverseVocabularyTree chld=this;
        char[] letters=word.toCharArray();
        for (int i=word.length()-1; i>=0; i--) {        
            if (!tr.childs.containsKey(letters[i]))
            {               
                for(int j=i; j>=0; j--) //a small optimisation..
                {
                    chld=new InverseVocabularyTree();
                    tr.childs.put(letters[j], chld);
                    tr=chld;
                }
                break;
            }
            else
            tr=tr.childs.get(letters[i]);
        }
        tr.description=description;         
        return;
    }

    public HashMap<Character, InverseVocabularyTree> getChilds() {
        return childs;
    }

    public String[] getRemovableBasicParts() {
        return removableBasicParts;
    }

    public LinkedList<String[]> getAllRemovableBasicParts() {
        LinkedList<String[]> ret=new LinkedList<String[]>();
        if (removableBasicParts!=null)
            ret.add(removableBasicParts);
        if (childs.keySet().isEmpty())
            return ret;
        for(char c : childs.keySet())
            ret.addAll(childs.get(c).getAllRemovableBasicParts());
        return ret;
    }   
}

那么,在这种情况下,有任何想法或建议如何优化?

3 个答案:

答案 0 :(得分:3)

如果您不需要值,我会使用NavigableMap或类似的Set。 假设你需要用“abc”搜索单词startign,你只需要做

NavigableMap<String, Boolean> wordmap = new TreeMap<String, Boolean>();
Random random = new Random(1);
for(int i=0;i<10*1000*1000;i++)
    wordmap.put(Long.toString(Math.abs(random.nextLong()), 36).substring(1), true);
String prefix = "abcd";
for (String word : wordmap.subMap(prefix, prefix+"\uffff").keySet()) {
    System.out.println(word + " starts with " + prefix);
}

//或

for (String word : wordmap.tailMap(prefix).keySet()) {
    if (!word.startsWith(prefix)) break;
    System.out.println(word + " starts with " + prefix);
}

这在我的机器上使用1GB,用于1000万个条目并打印

abcd0krpbk1 starts with abcd
abcd7xi05pe starts with abcd
abcdlw4pwfl starts with abcd
编辑:基于反馈,我建议采用以下方法。

// keys stored in reverse order of the original string.
NavigableMap<String, Boolean> wordmap
String search = "dcba";
// retains hte order keys were added.
Map<String, Boolean> results = new LinkedHashMap<String, Boolean>();
for(int i=search.size();i>=1;i--) {
    String s = search.substring(0, i);
    results.putAll(wordmap.subMap(s, s+'\uFFFF')); // ignores duplicates
}

结果将包含所有搜索的组合,从最具体到最不具体。     }

答案 1 :(得分:1)

假设问题是,在几十万字之后你的树太高了你可以尝试使用某些常见的二元组或三元组而不是单个字母用于几个节点以便制作它有点短。例如,如果你有很多以“ing”结尾的单词而不是为g生成一个n的子节点,那么n的子节点可以创建一个单独的节点。当然,这将有多好,取决于你的词汇量,你可能需要做一些分析,找到适当的双,三克使用。

一般情况下,既然你说你已经检查了垃圾收集,我认为如果你能找到一个特定的树大小,之后你的应用程序开始变慢或问题完全不同,那将会很有用。更好地了解问题究竟是什么可以为您提供有关如何解决问题的新想法。

答案 2 :(得分:1)

你正在为每个单词创建至少一个HashMap(通常更多) - 所以如果你有很多不同的单词,你的内存就会耗尽。请勿明确调用System.gc,而应使用jconsole或类似的分析工具观察您的程序。

我想在你的第一个300000字后,只是内存几乎已满,而你的程序大部分时间都在努力争取更多的空间。如果是这种情况,请尝试为程序提供更多内存(使用-Xmx选项)。