Fork me on GitHub

词频统计

这是我们数据结构课程最后的一次作业,词频统计。

其主要要求是这样的:

编写程序统计一个英文文本文件中每个单词的出现次数(词频统计),并将统计结果按单词出现频率由高至低输出到指定文件中。

看到这个要求,在给出数据规模与下一步要求时,本来大家肯定会想,统计词频嘛。就是套用一下trie树(字典树),或者更简单的,就是直接用数组或链表暴力查询求解嘛。

数据规模也不是特别大,据我们后来的估算,大概不同的单词数大概在1e5这个级别,总共的单词数在1e6这个级别。不过坑人的地方在于这样的一个评分方式:

在所有程序中运行正确且最快的将得满分,其它程序的得分以最快的程序运行时间为基准,根据其运行时间计算得出。

可谓没有对比就没有伤害,当别人的代码在0.1s跑出结果,而你的代码通过几秒甚至几十秒求解出来答案时,此时的分数就很不乐观了。

因此这也成为当时我们的各种绞尽心思的优化大作战,从发现系统测试的bug(3次测试只要有一次正确就算正确了,因此有的人故意只算对一次其它两次直接退出来换取时间)、到算法层次上的改进、到细枝末节(从数组的大小到每一句的优化编写等等),到了后期大家都在为了节约一点时间而想出各种花招。

我当时为了提升程序的性能、减少运行时间,也琢磨出了一些小的tip。比如用fread一下读入整个文件,比如手写memcpy函数,比如究竟是内联函数还是宏定义还是都不用,还有一些小的技巧了,不过这些都是非常细微的提升了。最主要的时间上差距还是在选择了什么样的算法、什么样的数据结构上。没有STL的C语言相对来说更多东西需要我们自己去实现。

在算法的选择上,经过几次试验后,选择的是字典树+基数排序的做法,之前曾经试过快排啊、桶排还有链表等等,效果都不是特别好。

尝试过的方法:

  • 内嵌汇编

    尝试着把一些大量重复操作的语句用汇编代码来替代,简单试了几次,不过就效果来说好像没有啥用,可能使用方式不是很合理或者也可能是,C编译器的优化已经足够快了吧。
    

觉得可行但没有尝试过的方法:

  • 多线程

    应该是一定可以提升性能的方法,不过当时没有实践。
    
  • Map Reduce

    一种并行化的处理,不过之前都没听说过,因此当时也没有用。
    

最终的代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define find(c) (c>=0) && (c<=25)
int p,tot,n,num[1500000],b[150000][41],first[80010],now[80010],next[80010],cnt[150010];
char x[150010][41],ans[41],s[8000010];
void dfs(int start,int k)
{
    int i,h,j;
    if (num[start]>0)
    {
        ans[k]='\0';
        n++;
        for (j = 0; j <= k; ++j)//strcpy(x[++n],ans);
            x[n][j] = ans[j];
        h = num[start];
        first[h] = (first[h]==0) ? n : first[h];
        next[now[h]] = n;
        now[h] = n;
    }
    for (i = 0; i < 26; ++i)
    if (b[start][i] != 0)
    {
        ans[k] = i+'a';
        dfs(b[start][i],k+1);
    }
}
int main(int argc, const char * argv[])
{
    FILE *in,*out;
    int i,l,j,z;
    in = fopen("article.txt","r");
    out = fopen("wordfreq.txt","w");
    
    l = fread(s,1,7000010,in);
    for (i = 0; i < l; ++i)
        s[i] = tolower(s[i])-'a';
    tot = i = 0;
    while (i < l)
    {
        p = 0 ;
        while (find(s[i]))
        {
                if (!b[p][s[i]])
                    b[p][s[i]] = (++tot);
                p = b[p][s[i]];
                i++;
        }
        num[p]++;
        i++;
    }
    
    n = 0;
    num[0] = 0;
    dfs(0,0);
    
    for (i = 60000; i >= 1; --i)
    for (j = first[i]; j != 0; j = next[j])
            fprintf(out,"%s %d\n",x[j],i);
    z = 0;
    for (i = 60000; i >= 1; --i)
    for (j = first[i]; j != 0 && z < 100; j = next[j])
    {
        printf("%s %d\n",x[j],i);
        z++;
    }
    return 0;
}


这个评测结果原来是实时的,一提交就能看到结果。不过后来这个平台被同学们找出各种减少时间的bug,老师不得不说最后会进行另一次科学合理的统一测试来确定最终成绩。而在经过统一测试后,我侥幸获得了5分的满分,虽然还是比第一名的同学时间要慢上一些的,不过还是不错的结果了。

结果截图
posted @ 2017-05-09 17:14  ohazyi  阅读(3307)  评论(0编辑  收藏  举报