对词频统计算法的代码实践

对词频统计的实践
这次的实践是由一道面试题引出:找出长篇文章中出现最多的单词。
最开始想到的方法是用map,每个单词为key值,单词出现的次数为value值,但是这样做法虽然简单,但是效率并不是很高。
之后又通过上网搜索和实践,使用了树形结构来统计每个单词出现的次数。
 
树的根节点为root,树中每个节点都有属性count会记录对应路径上的字符个数,路径则是从根节点指向其26个子节点的指针(每一条路径代表一个英文字母)。比如文章为“apple,apple,honey”,则第一个字符为a,那通过之前的算法,就在根节点root下创建一个孩子树,且将”根节点“转移到存有字符a的路径上指向的子节点,之后是字符p,依次往下,直到读到‘,’说明一个单词读完,这时会在当前子节点的属性进行count++;以此类推,会读完整个文章,之后用深度优先搜索进行遍历单词树,再将对应单词存在队列中。最后Collections来进行次数排序。

下面是树形结构的部分代码:
//单词树
public static CharTreeNode genCharTree(String text) {
CharTreeNode root = new CharTreeNode();
CharTreeNode p = root;
char c = 0;
for (int i = 0; i < text.length(); ++i) {
c = text.charAt(i);
// 采用ASCII码来区分字符 A65 Z90
if (c >= 'A' && c <= 'Z')
c = (char) (c + 'a' - 'A');
// a97 z122
if (c >= 'a' && c <= 'z') {
//当子节点为空时,创建孩子树
if (p.children[c - 'a'] == null)
p.children[c - 'a'] = new CharTreeNode();
p = p.children[c - 'a'];
} else {
//当解析出来是标点,读完一个单词,且单词计数加一,
p.count++;
p = root;
}
}
      
          //如果整篇文章的最后一个单词后没有标点,这里count会+1,如果有标点,这时的p也为根节点,count值加一也就没关系了。
if (c >= 'a' && c <= 'z')
P.count++;
    return root;
}

之后
// 使用深度优先搜索遍历单词树并将对应单词放入结果集中

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 类为存放单词及其次数
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 = genCharTree(article);
List result = new ArrayList();
getWordCountFromCharTree(result, root);
       //Collections对次数进行排序
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();
}
});
for (int i = 0; i < result.size(); i++) {
WordCount wc = (WordCount) result.get(i);
System.out.println(wc.getWord() + wc.getCount());
}
return result;
}

完整的代码会在之后上传附件。

还有对hashmap方法进行对比
// 统计每个单词出现的次数
public void choose(String expression) {
st = new StringTokenizer(expression, " ,.!");
while (st.hasMoreTokens()) {
String key = st.nextToken();
// 判断value存在时,代表原先就有key值,此时value次数加一
if (map.get(key) != null) {

map.put(key, map.get(key) + 1);

} else {
// 若key值不存在时,则将key值存入map,此时value的值表示出现一次
map.put(key, 1);
}
}
}

后续进行了两种方法时间上的对比,在短篇文章时,两个方法运行时间相同,但是长篇文章时,则单词树的方法效率更高。单词树的时间复杂度是O(nlogn),而用hashmap方法的时间复杂度是O(mlogn),m为字母数。

猜你喜欢

转载自shine-j.iteye.com/blog/2203892