7-44 基于词频的文件相似度 (30 分)
实现一种简单原始的文件相似度计算,即以两文件的公共词汇占总词汇的比例来定义相似度。为简化问题,这里不考虑中文(因为分词太难了),只考虑长度不小于3、且不超过10的英文单词,长度超过10的只考虑前10个字母。
输入格式:
输入首先给出正整数N(≤100),为文件总数。随后按以下格式给出每个文件的内容:首先给出文件正文,最后在一行中只给出一个字符#,表示文件结束。在N个文件内容结束之后,给出查询总数M(≤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%
思路: 一开始是打算直接来,写几个函数将文件中的不合法单词都过滤掉,然后再另一个文件中查找另一个文件的单词,最后计算结果,然而最后一个测试点被卡的死死的,无论我怎么优化总会超时,就这样这道题目卡了将近两天
以下是原来的代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<cctype>
using namespace std;
const int maxn = 1e4;
int N,M;
char File[110][maxn];
char word[11];
void extractword(char a[],int *ptr)//提取单词
{
memset(word,0,sizeof(word));
for(;*ptr < strlen(a);(*ptr)++){
int v = 0;
while(isalpha(a[*ptr]) && v < 10){
a[*ptr] = tolower(a[*ptr]);
word[v++] = a[(*ptr)++];
}
if(v >=3 && v <= 10){
return;
}
}
}
void filter(char a[]) // 过滤长度小于3的单词和长度大于10多于的字母
{
for(int i=0;i < strlen(a);i++){
int len = 0;
int ptri = i;
if(isalpha(a[i])){
while(isalpha(a[i])){
len++;
i++;
if(len > 10){
a[i - 1] = ' ';
}
}
if(len < 3){
while(ptri < i){
a[ptri++] = ' ';
}
}
}
else
a[i] = ' ';
}
}
bool Search(const char word[],string a[],int n)//二分查找法,查找两个文件中是否有相同的单词
{
int L = 0, R = n;
while(R > L){
int M = (R-L)/2 + L;
int f = strcmp(word,a[M].c_str());
if(f == 0)
return true;
else if(f > 0){
L = M + 1;
}
else{
R = M;
}
}
return false;
}
int main()
{
cin >> N;
for(int i=1;i <= N;i++){
int j = 0;
string c;
while((getline(cin,c)) && strcmp(c.c_str(),"#") != 0){
strncat(File[i],c.c_str(),c.length());//将一个文件中的内容拼接在一起
}
filter(File[i]); //过滤掉不合法的字母
}
cin >> M;
for(int i=0;i < M;i++){
int f1,f2;
scanf("%d%d",&f1,&f2);
int ptr = 0,n1 = 0,n2 = 0,same = 0;
string F1[100000],F2[100000];
while(ptr < strlen(File[f1])){
extractword(File[f1],&ptr); //每次提取出一个单词
if(strlen(word) >= 3 && !Search(word,F1,n1))
F1[n1++].append(word);//代表文件一的合法单词集
sort(F1,F1+n1);//排序后才能利用二分查找法
}
ptr = 0;
while(ptr < strlen(File[f2])){
extractword(File[f2],&ptr);
if(strlen(word) >= 3 && !Search(word,F2,n2))
F2[n2++].append(word);//文件二的合法单词集
sort(F2,F2+n2);
}
for(int j = 0;j < n2;j++){
if(Search(F2[j].c_str(),F1,n1)){
same++;
}
}
double x = same,y = n1 + n2 - same;
printf("%.1lf%%\n",x/y*100);
}
return 0;
}
虽然很想继续优化下去不过卡了太久了,不得不换个方法,然后想到了c++的关联容器set,代码变得非常简洁,看来以后要多熟悉c++中的容器了
改进后的代码:
#include<iostream>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<string>
#include<set>
using namespace std;
int N,M;
set<string> File[110];
void extractword(string word,set<string> &File)
{
for(int i=0;i < word.length();i++){//全部小写化
if(isalpha(word[i])){
word[i] = tolower(word[i]);
}
}
for(int i=0;i < word.length();i++){//给每个文件加入合法单词
int len = 0,ptri = i;
if(isalpha(word[i])){
while(isalpha(word[i])){
if(len >= 10){
word[i] = ' ';
}
else
len++;
i++;
}
if(len >= 3 && len <= 10){
File.insert(word.substr(ptri,len));
}
}
}
}
int main()
{
cin >> N;
for(int i=1;i <= N;i++){
string word;
while((getline(cin,word) && word != "#")){
extractword(word,File[i]);
}
}
cin >> M;
for(int i=0;i < M;i++){
int f1,f2;
int same = 0,n1,n2;
scanf("%d%d",&f1,&f2);
n1 = File[f1].size();
n2 = File[f2].size();
for(set<string>::iterator it = File[f1].begin();it != File[f1].end();it++){
if(File[f2].find(*it) != File[f2].end()){
same++;
}
}
double x = same,y = n1 + n2 - same;
printf("%.1lf%%\n",x/y*100);
}
return 0;
}