使用單詞樹進行詞頻統計演算法
阿新 • • 發佈:2019-02-07
許多英語培訓機構(如新東方)都會出幾本“高頻詞彙”的書,主要內容是統計近幾年來各類外語考試中屢次出現的高頻詞彙,幫助考生減少需要背的生詞的數量。但這些高頻是如何被統計出來的呢?顯然不會用手工去計算。
假如我們已經將一篇文章存在一字串(String)物件中,為了統計詞彙出現頻率,最簡單直接的做法是另外建一個Map:key是單詞,value是次數。將文章從頭讀到尾,讀到一個單詞就到Map裡查一下,如果查到了則次數加一,沒查到則往Map裡一扔。這樣做雖然程式碼寫起來簡單,但效能卻非常差。首先查詢Map的代價是O(logn),假設文章的字母數為m,則整個統計程式的時間複雜度為O(mlogn)不說,如果要拿高頻詞可能還需要對統計結果進行排序。即便對結構上進行優化效能仍然不高。如果能夠將時間複雜度從O(mlogn)減少到O(m)的話不是更好?
為了改進演算法我們首先引進單詞樹。與單詞字首樹不同,單詞樹的結構相當簡單,結構如圖所示:
從圖中我們可以看出,樹中每個結點儲存屬性值cnt與指向其26個子結點的指標(每一條路徑代表一個英文字母),其中cnt為到達該結點經過路徑所對應的英文單詞在文章中出現的次數。也就是說,我們開始讀文章時讓一個指標指向單詞數的根結點,之後每度一個字母就讓該指標指向當前結點對應路徑上的子結點(若子結點為空則新建一個),一個單詞讀完後讓當前結點的cnt值加一,並讓指標重新指向根結點。而當一篇文章讀完之後我們的單詞樹也就已經建立完畢了。之後只要去遍歷它並把取到的單詞根據次數進行排序就行了(時間複雜度為O(nlogn))。
程式程式碼如下,首先是存放單詞及出現次數的JavaBean
public class WordCount { private String word; private int count; public int getCount() { return count; } public void setCount(int count) { this.count = count; } public String getWord() { return word; } public void setWord(String word) { this.word = word; } } //其次是實現詞頻表生成演算法的類: public class WordCountService { /** * 根據文章生成單詞樹 * @param text * @return */ private static CharTreeNode geneCharTree(String text){ CharTreeNode root = new CharTreeNode(); CharTreeNode p = root; char c = ' '; for(int i = 0; i < text.length(); ++i){ c = text.charAt(i); if(c >= 'A' && c <= 'Z') c = (char)(c + 'a' - 'A'); if(c >= 'a' && c <= 'z'){ if(p.children[c-'a'] == null) p.children[c-'a'] = new CharTreeNode(); p = p.children[c-'a']; } else{ p.cnt ++; p = root; } } if(c >= 'a' && c <= 'z') p.cnt ++; return root; } /** * 使用深度優先搜尋遍歷單詞樹並將對應單詞放入結果集中 * @param result * @param p * @param buffer * @param length */ private static void getWordCountFromCharTree(List result,CharTreeNode p, char[] buffer, int length){ for(int i = 0; i < 26; ++i){ if(p.children[i] != null){ buffer[length] = (char)(i + 'a'); if(p.children[i].cnt > 0){ WordCount wc = new WordCount(); wc.setCount(p.children[i].cnt); wc.setWord(String.valueOf(buffer, 0, length+1)); result.add(wc); } getWordCountFromCharTree(result,p.children[i],buffer,length+1); } } } private static void getWordCountFromCharTree(List result,CharTreeNode p){ getWordCountFromCharTree(result,p,new char[100],0); } /** * 得到詞頻表的主演算法,供外部呼叫 */ public static List getWordCount(String article){ CharTreeNode root = geneCharTree(article); List result = new ArrayList();//此處也可用LinkedList連結串列,以避免陣列滿了擴容導致的效能損失 getWordCountFromCharTree(result,root); Collections.sort(result, new Comparator(){ public int compare(Object o1, Object o2) { WordCount wc1 = (WordCount)o1; WordCount wc2 = (WordCount)o2; return wc2.getCount() - wc1.getCount(); } }); return result; } } /**單詞樹結點的定義*/ class CharTreeNode{ int cnt = 0; CharTreeNode[] children = new CharTreeNode[26]; }