5-44 基于词频的文件相似度 (30分)
实现一种简单原始的文件相似度计算,即以两文件的公共词汇占总词汇的比例来定义相似度。为简化问题,这里不考虑中文(因为分词太难了),只考虑长度不小于3、且不超过10的英文单词,长度超过10的只考虑前10个字母。
输入格式:
输入首先给出正整数N(≤100),为文件总数。随后按以下格式给出每个文件的内容:首先给出文件正文,最后在一行中只给出一个字符#,表示文件结束。在N个文件内容结束之后,给出查询总数M(
104
10
4
),随后M行,每行给出一对文件编号,其间以空格分隔。这里假设文件按给出的顺序从1到N编号。
输出格式:
针对每一条查询,在一行中输出两文件的相似度,即两文件的公共词汇量占两文件总词汇量的百分比,精确到小数点后1位。注意这里的一个“单词”只包括仅由英文字母组成的、长度不小于3、且不超过10的英文单词,长度超过10的只考虑前10个字母。单词间以任何非英文字母隔开。另外,大小写不同的同一单词被认为是相同的单词,例如“You”和“you”是同一个单词。
输入样例:
3
Aaa Bbb Ccc
#
Bbb Ccc Ddd
#
Aaa2 ccc Eee
is at Ddd@Fff
#
2
1 2
1 3
输出样例:
50.0%
33.3%
思路
目前平台上这道题的通过人数为0,当然我也还没写出PASS的代码。
提前不知道文件大小,所以必须用链表动态存储数据。注意不区分大小写。对于长度大于10的字符串,只考虑前10个字母(不能用scan_s截断字符串),每读完一个文件都必须排序出一个单向链。
【160830更新】用链表写了一个,规则已经是对的了,对于超时,只能说简单的单向链表是不可取的的机构。下面可供扩展的还有二叉树(不过也有风险)。或者推翻整个程序架构,建立一个可扩容的哈希表存放结构,这个机构包括一个字符串以及这个字符串在哪些文件中出现过。
【180126更新】百转千回,scan函数重写了不知道多少遍,#可能出现的各种位置都考虑了。就是想不通最后一个测试为什么过不去。直到刚刚自己打了几个测试,测试到同一个文件的相似度,才发现自己灯下瞎(我的代码是在获取词汇的时候就直接统计该词汇在哪些文件中出现过,所以后面回答m组查询的时候我是可以直接调取Same数组中的结果的。形如same[i][i]的位置的值都是0)
单向链结构:(超时)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define HashLenth 10007 //测试内存开销3MB,也就是不到1MB的数据个数。
/* 评测结果
提交时间 状态 分数 题目 编译器 耗时 用户
2018/1/26 17:49:32 答案正确 30 7-44 C (gcc) 75 ms 569985011
测试点 提示 结果 耗时 内存
0 sample 答案正确 2 ms 244KB
1 同一文件内重复出现的单词不重复计算;太长的单词只考虑前10个字母 答案正确 2 ms 244KB
2 完全不同 答案正确 2 ms 256KB
3 完全相同 答案正确 2 ms 152KB
4 最大N和M;有1个文件包含了全部单词;有1个单词出现在所有文件里 答案正确 75 ms 2032KB*/
typedef struct node *Node;
struct node {
char*Word;
int Reached[101];
int Times;
};
Node Hash[HashLenth]= {0};
int Same[101][101]= {0};
int Sum[101]= {0};
int n;
int Mod(int);
char*scan();
void Insert(int ,char*);
int HashKey(char*);
void Refresh(Node,int);
int main() {
scanf("%d",&n);
for(int j=0; j<n; j++) {
while(1) {
char*temp=scan();
if(temp) {
if(temp[0]=='#') {
// printf("+EOF+");
break;
}
Insert(j+1,temp);
}
}
}
int m;
scanf("%d",&m);
while(m--) {
int a,b;
scanf("%d%d",&a,&b);
if(a==b)printf("100.0%%\n");//测试了老半天才发现这个坑。。。 看看同期的PTA题目,这个漏洞1年了才发现
else
printf("%.1f%%\n",Same[a][b]*100.0/(Sum[a]+Sum[b]-Same[a][b]));
}
return 0;
}
void Refresh(Node N,int h) {
if(N->Reached[N->Times]==h)return;//同一个文件里面出现的重复单词不累加
++Sum[h];//文件词汇量+1
// printf("[Used");
N->Reached[++N->Times]=h;
for(int i=0; i<N->Times; i++) {//所有包含此字符串的文件彼此Same++
// printf("*");
++Same[N->Reached[i]][h];
++Same[h][N->Reached[i]];
}
// printf("]");
}
int HashKey(char*K) {//尽量不让他抱团
int temp=(K[0]-'a')*32*32+(K[1]-'a')*32+K[2]-'a';
temp*=HashLenth;
temp/=27482;//26*32*32+26*32+26
return temp;
}
int Mod(int Num) {
while(Num<0)Num+=HashLenth;
return Num%HashLenth;
}
void Insert(int h ,char*K) {
int Key=HashKey(K);
int flag;
int i;
for( i=0; i<=HashLenth/2; i++) {
flag=Mod(Key+i*i);
if(!Hash[flag])break;
else if(!strcmp(Hash[flag]->Word,K)) {
Refresh(Hash[flag],h);
return ;
}
flag=Mod(Key-i*i);
if(!Hash[flag])break;
else if(!strcmp(Hash[flag]->Word,K)) {
Refresh(Hash[flag],h);
return ;
}
}
if(i>HashLenth/2) {//实践证明平台的词汇量并不多
exit(1);// printf("ERROR:HashLenth is not enouth!\n");
}
// printf("[NEW]");
++Sum[h];//对该文件词汇量+1
Hash[flag]=(Node)malloc(sizeof(struct node));
Hash[flag]->Word=(char*)malloc(sizeof(char)*strlen(K));
strcpy(Hash[flag]->Word,K);
Hash[flag]->Times=0;
Hash[flag]->Reached[0]=h;
}
char*scan() {
// printf("#");
static char temp[11];
static int Flag_br=0;
int i=0;
char c;
while(1) {
c=getchar();
switch(c) {
case '#' :
if(Flag_br==1) {
c=getchar();
if(c=='\n') {
temp[0]='#';
temp[1]='\0';
return temp;
}
}
default:
switch(c) {
case 'a'...'z':
if(i<10)
temp[i++]=c;
break;
case 'A'...'Z':
if(i<10)
temp[i++]=c-'A'+'a';
break;
default:
if(c=='\n')Flag_br=1;
else Flag_br=0;
temp[i]='\0';
if(i>2) {
// printf("{%s}",temp);
return temp;
} else {
// printf("{}");
return NULL;
}
break;
}
break;
}
}
}