7-18 词频统计(30 分)
请编写程序,对一段英文文本,统计其中所有不同单词的个数,以及词频最大的前10%的单词。
所谓“单词”,是指由不超过80个单词字符组成的连续字符串,但长度超过15的单词将只截取保留前15个单词字符。而合法的“单词字符”为大小写字母、数字和下划线,其它字符均认为是单词分隔符。
输入格式:
输入给出一段非空文本,最后以符号#
结尾。输入保证存在至少10个不同的单词。
输出格式:
在第一行中输出文本中所有不同单词的个数。注意“单词”不区分英文大小写,例如“PAT”和“pat”被认为是同一个单词。
随后按照词频递减的顺序,按照词频:单词
的格式输出词频最大的前10%的单词。若有并列,则按递增字典序输出。
输入样例:
This is a test.
The word "this" is the word with the highest frequency.
Longlonglonglongword should be cut off, so is considered as the same as longlonglonglonee. But this_8 is different than this, and this, and this...#
this line should be ignored.
输出样例:(注意:虽然单词the
也出现了4次,但因为我们只要输出前10%(即23个单词中的前2个)单词,而按照字母序,the
排第3位,所以不输出。)
23
5:this
4:is
感谢武汉理工大学的郭小兵老师修正测试数据!
根据题目知,单词最大大小为80 所以如果一个字符串长度是90 那么他可能就是俩个单词了
单词只能由大小写字母 和数字和下划线组成 其余被认为是分隔符
这次个人感觉还是用数组 Hash 比较好,但是做的过程中觉得链表较好 O(∩_∩)O;
还有大小写转换 巴拉巴拉;
补充一个小知识点,scanf(“%[a-z,A-Z,0-9]”)真的是可以为所欲为的 scanf 有很多用法和扩展 真的很好用,我应该会总结一篇过于scanf 的用法拓展吧;
此题我用到 哈希 .最大堆和SCANF的特殊输入
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <ctype.h>
#define MAX 1000000
typedef struct Node *Hash; /**哈希的结构体**/
struct Node{
char *s;
int num;
Hash next;
};
typedef struct node *heap; /**最大堆的结构体**/
struct node{
Hash *h;
int size;
};
int num=0;
int nextprme(int n); /**本来应该找到一个合适的大小来建立哈希数组的 但是题目没有给出单词个数,
再扫一遍输入内容来找单词个数,代价太高,所以 不如直接定义最大的指针数组 用链表来做**/
int deal(char *s) /**哈希日常处理。。。把数据映射成相应下标 ,这个函数可以根据自己喜好 自己设置处理方式,只要能通过的话。。。(●’◡’●)**/
{
/**此方法是数据结构课本上一个移位法,就是把字符串每一位当一个32进制处理,但是最多能移位处理12次,所以我们需要改进一下**/
int i, len=strlen(s);
unsigned int h=0;
if(len<=12)
{
while(*s!=’\0′)
h=((h<<5)+*(s++))%MAX;
}
else /**ARE YOU KIDDING ME? ARE THEY DIFFERENT? **/ // enummm….嗯~ o(* ̄▽ ̄*)o
{
while(*s!=’\0′)
h=((h<<5)+*(s++))%MAX;
}
return h;
}
Hash insert(Hash h,char *s) /**哈希链表的老朋友 没有不行**/
{
Hash p=h; /**先判断是不是空**/
while(p)
{
if(strcmp(p->s,s)==0) /**不是空就看是不是相等**/
{
p->num++;
return h;
}
else p=p->next; /**否则next**/
}
p=(Hash)malloc(sizeof(struct Node)); /**是空 就好说了,在头指针那插一个就好了,在后面插可以但是 代码会长那么一点**/
p->s=(char*)malloc(strlen(s)*sizeof(char)); /**分配相应的字符串空间**/
strcpy(p->s,s); /**复制**/
p->num=1;
p->next=h;
num++;
//printf(“%s %d\n”,p->s,p->num); /**测试 点 看看插入的是啥 ,感兴趣的可以\\去掉**/
return p;
}
void display(heap H,Hash h) /**这是为了测试代码正确 可忽略这函数 吗?!(13:56) 不 ! 经过我的改进 他成为 了最大堆的输入函数 (●’◡’●)(16:42)**/
{
if(h==NULL)
return;
else
while(h)
{
H->h[++H->size]=h;
h=h->next;
}
}
void shift(char *s) /**转换 将读到的大写字母转换成小写字母**/
{
int i;
for(i=0;i<strlen(s);i++)
{
s[i]=tolower(s[i]);
}
}
/**下面是最大堆的函数了**/
heap Creatheap(int p)
{
heap he=(heap)malloc(sizeof(struct node)); /** 建立最大堆**/
he->h=(Hash*)malloc((p+5)*sizeof(Hash)); /**建立 一个 由哈希指针 组成的 数组**/
he->size=0;
return he;
}
int max (Hash x,Hash y) /** s 是单词 ,num 是 出现次数**/
{
if(x->num==y->num)
{
if(strcmp(x->s,y->s)<0) return 1;
else return 0;
}
else if(x->num>=y->num) return 1;
else return 0;
}
void perdown(heap H,int p) /**这属于最大堆的,说不好说 ,如果用笔 按照二叉树那样画一画 就明白了 ,不知道二叉树的,先画个TREE 再旋转180° 不知道TREE是啥的,出门右拐加抬头。**/
{
int parent,child;
Hash x=H->h[p];
for(parent=p;parent*2<=H->size;parent=child)
{
child = parent*2;
if(child!=H->size&&!(max(H->h[child],H->h[child+1])))
child++;
if(max(x,H->h[child])) break;
else
H->h[parent] = H->h[child];
}
H->h[parent]=x;
}
void Build(heap H) /**这个要和上面那个函数连在一起 ,他们就成了(最大堆的建立)**/
{
int i;
for(i=H->size/2;i>0;i–)
perdown(H,i);
}
Hash Delete(heap H) /**最大堆的删除 其实就是把最大值取出来,就跟取抽纸一样,抽完,下一个最大值又到刚才的位置了**/
{
int parent,child;
Hash MaxHash,x;
MaxHash=H->h[1]; /**将最大值 抽出,最大值就是 排在头上的第一个的the first ’s**/
x=H->h[H->size–]; /**然后把欺负最小的,把老末放在[1]中,然后经过各种比他大的大大蹂躏,到达一个新的位置**/
for(parent =1;parent*2<=H->size;parent=child) /**蹂躏开始**/
{
child = parent*2;
if((child !=H->size)&&!(max(H->h[child],H->h[child+1])))
child++;
if(max(x,H->h[child])) break; /**蹂躏结束**/
else
H->h[parent]=H->h[child]; /**下一个继续蹂躏**/
}
H->h[parent]=x; /**到达不会被蹂躏的位置**/
return MaxHash; /**把那个快被遗忘的最大值返回 (可算有存在感了。。。)**/
}
Hash h[MAX]={0}; /**建立一个哈希指针数组,其实 一个循环 把每个都赋值为NULL比较好 ,我这种写法是因为me lan**/
int main(){
int p=MAX;
char arr[80];
int f;
while(1)
{
/**下面 是scanf 的特殊使用**/
f=scanf(“%80[A-Za-z0-9_]”,arr); /**这是最靓的地方 最多输80个 而且只能输入[A-Z a-z 0-0 _]中的字符,而且 如果scanf会返回一个已经读入的(连续的东西的)个数 ,注意是连续,80个字符串的读入也算只读入一个东西**/
arr[15]=’\0′; /**超过15的单词截取前15个 那就在第16个那个地方 赋值一个终止符**/
if(getchar()==’#’) /**将结束上次输入的字符GET掉 还有读入# 就**/
{
break;
}
if(f==0) continue; /**如果上次没有输入成功 那就不插入了,top(如果上次没输入成功,arr还保存这上上次输入的数据)**/
shift(arr); /**变变变,大写变小写**/
int pos = deal(arr); /**把数据处理一下,得到一个地址**/
h[pos]=insert(h[pos],arr); /**根据地址插入哈希链表**/
} /**到此为止哈希的链表差不多就实现了**/
/**接下来就是输出了,最大堆准备***/
int i;
heap H = Creatheap( num ); /**这里就知道上上上面的那个num 的用处了吧,根据单词个数,建立最合适堆,不浪费太多空间**/
for(i=0;i<MAX;i++) /**将链表中的东西先全搬进堆里**/
{
display(H,h[i]);
}
/*for(i=0;i<num;i++) /**测试点,看看搬进了啥**/
{
printf(“%s %d\n”,H->h[i]->s,H->h[i]->num);
}
printf(“———————–\n”);*/
Build(H);
Hash x;
/*for(i=0;i<num;i++) /**测试点,看看从数组变成最大堆后,成啥样了,(看了也白看,就看个热闹,看不出他是不是最大堆了,得用手画一下才行。。)**/
{
printf(“%s %d\n”,H->h[i]->s,H->h[i]->num);
}
printf(“———————–\n”);*/
printf(“%d\n”,num);
for(i=0;i<num/10;i++) /**可以把10去掉,看看是不是完整的从大到小输出**/
{
x=Delete(H);
printf(“%d:%s\n”,x->num,x->s);
}
return 0;
}
完毕
关于scanf的特殊用法,我会另开一页专门总结一下,毕竟太碎,太难记了