Java词频统计算法(使用单词树)

来源:互联网 发布:php 文件上传方法 编辑:程序博客网 时间:2024/05/01 13:51

  许多英语培训机构(如新东方)都会出几本“高频词汇”的书,主要内容是统计近几年来各类外语考试中屡次出现的高频词汇,帮助考生减少需要背的生词的数量。但这些高频是如何被统计出来的呢?显然不会用手工去计算。
  假如我们已经将一篇文章存在一字符串(String)对象中,为了统计词汇出现频率,最简单直接的做法是另外建一个Map:key是单词,value是次数。将文章从头读到尾,读到一个单词就到Map里查一下,如果查到了则次数加一,没查到则往Map里一扔。这样做虽然代码写起来简单,但性能却非常差。首先查询Map的代价是O(logn),假设文章的字母数为m,则整个统计程序的时间复杂度为O(mlogn)不说,如果要拿高频词可能还需要对统计结果进行排序。即便对结构上进行优化性能仍然不高。如果能够将时间复杂度从O(mlogn)减少到O(m)的话不是更好?
  为了改进算法我们首先引进单词树。与单词前缀树不同,单词树的结构相当简单,结构如图所示:

 

  从图中我们可以看出,树中每个结点保存属性值cnt与指向其26个子结点的指针(每一条路径代表一个英文字母),其中cnt为到达该结点经过路径所对应的英文单词在文章中出现的次数。也就是说,我们开始读文章时让一个指针指向单词数的根结点,之后每度一个字母就让该指针指向当前结点对应路径上的子结点(若子结点为空则新建一个),一个单词读完后让当前结点的cnt值加一,并让指针重新指向根结点。而当一篇文章读完之后我们的单词树也就已经建立完毕了。之后只要去遍历它并把取到的单词根据次数进行排序就行了(时间复杂度为O(nlogn))。

  程序代码如下,首先是存放单词及出现次数的JavaBean

  1. public class WordCount {
  2.     private String word;
  3.     
  4.     private int count;
  5.     public int getCount() {
  6.         return count;
  7.     }
  8.     public void setCount(int count) {
  9.         this.count = count;
  10.     }
  11.     public String getWord() {
  12.         return word;
  13.     }
  14.     public void setWord(String word) {
  15.         this.word = word;
  16.     }
  17. }

  其次是实现词频表生成算法的类:

  1. public class WordCountService {
  2.     
  3.     /**
  4.      * 根据文章生成单词树
  5.      * @param text
  6.      * @return
  7.      */
  8.     private static CharTreeNode geneCharTree(String text){
  9.         CharTreeNode root = new CharTreeNode();
  10.         CharTreeNode p = root;
  11.         char c = ' ';
  12.         for(int i = 0; i < text.length(); ++i){
  13.             c = text.charAt(i);
  14.             if(c >= 'A' && c <= 'Z')
  15.                 c = (char)(c + 'a' - 'A');
  16.             if(c >= 'a' && c <= 'z'){
  17.                 if(p.children[c-'a'] == null)
  18.                     p.children[c-'a'] = new CharTreeNode();
  19.                 p = p.children[c-'a'];
  20.             }
  21.             else{
  22.                 p.cnt ++;
  23.                 p = root;
  24.             }
  25.         }
  26.         if(c >= 'a' && c <= 'z')
  27.             p.cnt ++;
  28.         return root;
  29.     }
  30.     
  31.     /**
  32.      * 使用深度优先搜索遍历单词树并将对应单词放入结果集中
  33.      * @param result
  34.      * @param p
  35.      * @param buffer
  36.      * @param length
  37.      */
  38.     private static void getWordCountFromCharTree(List result,CharTreeNode p, char[] buffer, int length){
  39.         for(int i = 0; i < 26; ++i){
  40.             if(p.children[i] != null){
  41.                 buffer[length] = (char)(i + 'a');
  42.                 if(p.children[i].cnt > 0){
  43.                     WordCount wc = new WordCount();
  44.                     wc.setCount(p.children[i].cnt);
  45.                     wc.setWord(String.valueOf(buffer, 0, length+1));
  46.                     result.add(wc);
  47.                 }
  48.                 getWordCountFromCharTree(result,p.children[i],buffer,length+1);
  49.             }
  50.         }
  51.     }
  52.     
  53.     private static void getWordCountFromCharTree(List result,CharTreeNode p){
  54.         getWordCountFromCharTree(result,p,new char[100],0);
  55.     }
  56.     
  57.     /**
  58.      * 得到词频表的主算法,供外部调用
  59.      * @param article
  60.      * @return
  61.      */
  62.     public static List getWordCount(String article){
  63.         CharTreeNode root = geneCharTree(article);
  64.         List result = new ArrayList();//此处也可用LinkedList链表,以避免数组满了扩容导致的性能损失
  65.         getWordCountFromCharTree(result,root);
  66.         Collections.sort(result, new Comparator(){
  67.             public int compare(Object o1, Object o2) {
  68.                 WordCount wc1 = (WordCount)o1;
  69.                 WordCount wc2 = (WordCount)o2;
  70.                 return wc2.getCount() - wc1.getCount();
  71.             }
  72.         });
  73.         return result;
  74.     }
  75. }
  76. /**
  77.  * 单词树结点的定义
  78.  * @author FlameLiu
  79.  *
  80.  */
  81. class CharTreeNode{
  82.     int cnt = 0;
  83.     CharTreeNode[] children = new CharTreeNode[26];
  84. }