实验目的:
掌握跳表指针(skip pointers)技术、邻近搜索(proximity search)技术、编辑距离(edit distance)计算方法、基于发音的矫正技术(phonetic correction)等,了解PorterStemmer开源程序的简单调用。
实验要求:
(1). 考虑利用如下带有跳表指针的倒排记录表
和两个中间结果表(如下所示,不存在跳表指针)分别进行合并操作。
3 5 9 96 99 100 101
25 60 120 150
采用教材《Introduction to Information Retrieval》第37页Figure 2.10中所描述的基于跳表指针(skip pointers)的倒排记录表(postings lists)合并算法,请问:
a.跳表指针实际跳转的次数分别是多少(也就是说,指针p1的下一步将跳到skip(p1))?
b.当两个表进行合并时,倒排记录之间的比较次数分别是多少?
c.如果不使用跳表指针,那么倒排记录之间的比较次数分别是多少?
(2). 下面给出的是一个位置索引的一部分,格式为:
<position1, position2, …>; doc2: <position1, position2, …>; …
angels: 2: <36,174,252,651>; 4: <12,22,102,432>; 7: <17>;
fools: 2: <1,17,74,222>; 4: <8,78,108,458>; 7: <3,13,23,193>;
fear: 2: <87,704,722,901>; 4: <13,43,113,433>; 7: <18,328,528>;
in: 2: <3,37,76,444,851>; 4: <10,20,110,470,500>; 7: <5,15,25,195>;
rush: 2: <2,66,194,321,702>; 4: <9,69,149,429,569>; 7: <4,14,404>;
to: 2: <47,86,234,999>; 4: <14,24,774,944>; 7: <199,319,599,709>;
tread: 2: <57,94,333>; 4: <15,35,155>; 7: <20,320>;
where: 2: <67,124,393,1001>; 4: <11,41,101,421,431>; 7: <16,36,736>;
请问哪些文档和以下的查询匹配?其中引号内的每个表达式都是一个短语查询。
a.“angels fear”
b.“angels fear to tread”
c.“angels fear to tread”AND“fools rush in”
(3). 阅读教材《Introduction to Information Retrieval》第37页Figure 2.10中所描述的基于跳表指针(skip pointers)的倒排记录表(postings lists)合并算法,并用Java语言或其他常用语言实现该算法。要求在题(1)的例子上验证算法的正确性。
(4).阅读教材《Introduction to Information Retrieval》第42页Figure 2.12中所描述的邻近搜索(proximity search)中的两个倒排记录表(postings lists)的合并算法,并用Java语言或其他常用语言实现该算法,并按要求做适当改进。要求使用附件“HW2.txt”中的60个文档(每行表示一个document,按空格切词,文档中的单词全部转换为小写)建立positional index,两个词项之间的间距(注:相邻的两个词项的间距为1)的形式包括以下三种情形(x是一个正整数):“-x”、“+x”和“x”,其中,“-x”表示第一个词项在第二个词项的左侧且间隔在x之内,“+x”表示第一个词项在第二个词项的右侧且间隔在x之内,“x”表示第一个词项与第二个词项的间隔(左侧和右侧均可)在x之内。要求在以下例子上验证算法的正确性:
(ranking, filtering, -4), (ranking, filtering, -5), (ranking, filtering, -6), (ranking, filtering, -7), (heterogeneous, learning, +2), (recommendation, bias, 2)。
(5).阅读教材《Introduction to Information Retrieval》第59页Figure 3.5中所描述的基于动态规划(dynamic programming)来计算两个字符串的编辑距离(edit distance)的算法,并用Java语言或其他常用语言实现该算法。要求计算以下15组单词的编辑距离:
(business, buisness)
(committee, commitee)
(conscious, concious)
(definitely, definately)
(fluorescent, florescent)
(forty, fourty)
(government, goverment)
(idiosyncrasy, idiosyncracy)
(immediately, immediatly)
(millennium, millenium)
(noticeable, noticable)
(tendency, tendancy)
(truly, truely)
(weird, wierd)
(privilege, privledge)
实验过程:
(1). 考虑利用如下带有跳表指针的倒排记录表
和两个中间结果表(如下所示,不存在跳表指针)分别进行合并操作。
3 5 9 96 99 100 101
25 60 120 150
采用教材《Introduction to Information Retrieval》第37页Figure 2.10中所描述的基于跳表指针(skip pointers)的倒排记录表(postings lists)合并算法,请问:
a.跳表指针实际跳转的次数分别是多少(也就是说,指针p1的下一步将跳到skip(p1))?
b.当两个表进行合并时,倒排记录之间的比较次数分别是多少?
c.如果不使用跳表指针,那么倒排记录之间的比较次数分别是多少?
a.跳表指针实际跳转的次数分别是多少(也就是说,指针p1的下一步将跳到skip(p1))?
1、和3 5 9 96 99 100 101进行合并操作:
实际跳转次数为2次,分别为24→75和75→92
2、和25 60 120 150进行合并操作:
实际跳转次数为3次,分别为3→24,75→92和92→115
b.当两个表进行合并时,倒排记录之间的比较次数分别是多少?
1、和3 5 9 96 99 100 101进行合并操作:
一共13次比较:
3和3
5和5
9和9
15和96
24和96
(利用跳表指针跳转)
75和96
(利用跳表指针跳转)
92和96
(利用跳表指针尝试跳转)
115和96
96和96
97和99
100和99
100和100
115和101
2、和25 60 120 150进行合并操作:
一共10次比较:
3和25
(利用跳表指针跳转)
24和25
(利用跳表指针尝试跳转)
75和25
39和25
39和60
60和60
68和120
75和120
(利用跳表指针跳转)
92和120
(利用跳表指针跳转)
115和120
c.如果不使用跳表指针,那么倒排记录之间的比较次数分别是多少?
1、和3 5 9 96 99 100 101进行合并操作:
18次。.
2、和25 60 120 150进行合并操作:
18次。.
(2). 下面给出的是一个位置索引的一部分,格式为:
<position1, position2, …>; doc2: <position1, position2, …>; …
angels: 2: <36,174,252,651>; 4: <12,22,102,432>; 7: <17>;
fools: 2: <1,17,74,222>; 4: <8,78,108,458>; 7: <3,13,23,193>;
fear: 2: <87,704,722,901>; 4: <13,43,113,433>; 7: <18,328,528>;
in: 2: <3,37,76,444,851>; 4: <10,20,110,470,500>; 7: <5,15,25,195>;
rush: 2: <2,66,194,321,702>; 4: <9,69,149,429,569>; 7: <4,14,404>;
to: 2: <47,86,234,999>; 4: <14,24,774,944>; 7: <199,319,599,709>;
tread: 2: <57,94,333>; 4: <15,35,155>; 7: <20,320>;
where: 2: <67,124,393,1001>; 4: <11,41,101,421,431>; 7: <16,36,736>;
请问哪些文档和以下的查询匹配?其中引号内的每个表达式都是一个短语查询。
a.“angels fear”
b.“angels fear to tread”
c.“angels fear to tread”AND“fools rush in”
a.“angels fear”
和这个查询匹配的文档需要同时存在angels和fear的记录。并且fear记录的位置应该在angels记录位置后一位。
因此文档4、7符合要求。
文档4的12、13位,以及432、433位为“angels fear”
文档7的17、18位为“angels fear”
b.“angels fear to tread”
和这个查询匹配的文档需要同时存在这四个词的记录,并且anegls、fear、to和tread四个词的位置应是连续的。
因此文档4符合要求。
文档4的12、13、14、15位为“angels fear to tread”
c.“angels fear to tread”AND“fools rush in”
考虑“fools rush in”短语查询。和这个查询匹配的文档需要同时存在fools、rush和in的记录,并且三个词的位置应是连续的。
因此文档2、4、7符合要求。
文档2的第1、2、3位,文档4的8、9、10位,文档7的3、4、5位和13、14、15位符合要求。
考虑到“angels fear to tread”查询结果为文档4。将两个短语查询的结果合并。得到和“angels fear to tread”AND“fools rush in”查询结果匹配的文档为文档4。
(3). 阅读教材《Introduction to Information Retrieval》第37页Figure 2.10中所描述的基于跳表指针(skip pointers)的倒排记录表(postings lists)合并算法,并用Java语言或其他常用语言实现该算法。要求在题(1)的例子上验证算法的正确性。
参考以下的伪代码编写基于跳表指针的倒排记录表合并算法。
首先输入带跳表指针的倒排记录表和中间结果表表长。并根据输入的跳表指针设置间隔在倒排记录表中放置跳表指针。跳表指针是否存在以及值,通过一个常数变量存放。默认为-1,即跳表指针不存在。若跳表指针存在,则变量记录跳表指针指向的文档编号在数组中的下标。
以下是根据伪代码编写的C++语言算法实现。无法显示完全的注释为“如果有跳表指针且跳转目标数仍然更小”。注意这里需要考虑特殊情况:跳表指针完成比较并跳转后已经到达记录末尾,无须进行多余的比较,可以直接终止程序,避免计算多余的比较次数。
计算带跳表指针的倒排记录表比较次数的代码为:
#include <bits/stdc++.h>
using namespace std;
int findword(vector <string> Lexicon,string word){//找到返回序号,没找到返回-1
int len=Lexicon.size();
int i;
for(i=0;i<len;i++)
if(Lexicon[i]==word)
return i;
return -1;
}
struct position{
int data;
int skippointer;
};
int main(){
int n1,n2,interval;//分别为带跳表指针的倒排索引表长,中间结果表表长,跳表指针放置间隔
position wordlist[99];
int intermediate[20];
cin>>n1>>n2>>interval;
int i,j;
int sum=0;//存储比较次数
cout<<"请输入数据:"<<endl;
for(i=0;i<n1;i++)//输入倒排索引表
cin>>wordlist[i].data;
cout<<"成功输入倒排索引表"<<endl;
for(i=0;i<n2;i++)//输入中间结果表
cin>>intermediate[i];
cout<<"成功输入中间结果表"<<endl;
for(i=0;i<n1;i++)
if(i%interval==0&&i+interval<n1)//在预定位置设置跳表指针
wordlist[i].skippointer=i+interval;
else//不设置跳表指针
wordlist[i].skippointer=-1;
cout<<"载入跳表指针,开始运算..."<<endl;
//↑输入数据
//↓基于跳表指针的倒排索引表合并算法
int pos1=0,pos2=0;//两个指针
vector <int> answer;//存储符合条件的文档ID
while(pos1<n1&&pos2<n2){//两个指针均未到达倒排文件尾
if(wordlist[pos1].data==intermediate[pos2]){//如果文档记录相等,即找到满足条件的文档ID
answer.push_back(wordlist[pos1].data);
pos1++,pos2++;
}
else{
if(wordlist[pos1].data<intermediate[pos2])//带跳表指针的倒排索引表的当前记录较小
if(wordlist[pos1].skippointer!=-1&&wordlist[wordlist[pos1].skippointer].data<=intermediate[pos2])//如果有跳表指针且跳转目标数仍然更小
while(wordlist[pos1].skippointer!=-1&&wordlist[wordlist[pos1].skippointer].data<=intermediate[pos2]){
pos1=wordlist[pos1].skippointer;
sum++;
}
else
pos1++;
else//中间结果表当前记录较小
pos2++;
}
if(pos1>=n1||pos2>=n1)//防止跳表指针执行完毕后到达记录末端多算比较次数
break;
sum++;
}
cout<<"检索结果为:";
for(int i=0;i<answer.size();i++)
cout<<answer[i]<<" ";
cout<<endl;
cout<<"比较次数为:"<<sum<<endl;
}
运行结果截图和详细的文字说明(1):
带有跳表指针的倒排记录表
和3 5 9 96 99 100 101的合并操作
与(1)中计算结果互相对照,可见程序很好地完成了任务,统计了比较次数并输出了正确的合并结果。
需要注意的是,即使跳表指针指向的文档编号更大而无法跳转,本次比较也需要记录为比较次数。
运行结果截图和详细的文字说明(2):
带有跳表指针的倒排记录表
和25 60 120 150的合并操作
与(1)中计算结果互相对照,可见程序很好地完成了任务,统计了比较次数并输出了正确的合并结果。
(4). 阅读教材《Introduction to Information Retrieval》第42页Figure 2.12中所描述的邻近搜索(proximity search)中的两个倒排记录表(postings lists)的合并算法,并用Java语言或其他常用语言实现该算法,并按要求做适当改进。要求使用附件“HW2.txt”中的60个文档(每行表示一个document,按空格切词,文档中的单词全部转换为小写)建立positional index,两个词项之间的间距(注:相邻的两个词项的间距为1)的形式包括以下三种情形(x是一个正整数):“-x”、“+x”和“x”,其中,“-x”表示第一个词项在第二个词项的左侧且间隔在x之内,“+x”表示第一个词项在第二个词项的右侧且间隔在x之内,“x”表示第一个词项与第二个词项的间隔(左侧和右侧均可)在x之内。要求在以下例子上验证算法的正确性:(ranking, filtering, -4), (ranking, filtering, -5), (ranking, filtering, -6), (ranking, filtering, -7), (heterogeneous, learning, +2), (recommendation, bias, 2)。
采用以下的三层容器嵌套会导致程序报错。
结构体定义:
倒排文件组织方式:
使用:
报错内容:
因此我们采用其他方法实现位置索引。注意到容器嵌套给初始化分配内存带来困难,而直接全初始化成定长容器则浪费的容器的作用。考虑到文本总长有限,不如大道至简,直接用数组来解决问题。注意!这种做法仅适用于本题数据量不大的情况!
用以下代码输出位置索引文件:
具体建立位置索引算法过程如下:
建立的位置索引(部分)如下:
参考以下的伪代码编写邻近搜索中倒排记录表的合并算法。
具体实现如下:
#include <bits/stdc++.h>
using namespace std;
int findword(vector <string> Lexicon,string word){//找到返回序号,没找到返回-1
int len=Lexicon.size();
int i;
for(i=0;i<len;i++)
if(Lexicon[i]==word)
return i;
return -1;
}
struct ans{//三元组:文档ID,词项在p1位置,词项在p2位置
int docID;
int position1;
int position2;
ans(int a,int b,int c){
docID=a;
position1=b;
position2=c;
}
void show(){
cout<<"< "<<docID<<" , "<<position1<<" , "<<position2<<" >"<<endl;
}
};
/*
注:本代码文件包含例子1~6六题分段代码和两种输出倒排索引的代码。这些代码均已注释。需要测试哪段代码只需取消注释即可。
*/
void searchpos(vector <string> Lexicon,int Invertedfile[][50][5],string str1,string str2,int x,int isabs){//isabs表明是不是两侧有效
vector <ans> answer;//存储符合条件的文档ID
int loc1=findword(Lexicon,str1);
int loc2=findword(Lexicon,str2);//两个指针,指向单词记录
cout<<"检索"<<str1<<", "<<str2<<", "<<x<<endl;
int pos1=1,pos2=1;//两个指针,指向文档记录
int docsize1=Invertedfile[loc1][0][0];//loc1单词记录下文档记录的数目
int docsize2=Invertedfile[loc2][0][0];
while(pos1<=docsize1&&pos2<=docsize2){
if(Invertedfile[loc1][pos1][0]==Invertedfile[loc2][pos2][0]){//如果找到满足条件的文档ID,即两个词项同时在当前文档出现
int pp1=2,pp2=2;//两个指针,指向位置索引记录
int possize1=Invertedfile[loc1][pos1][1];//loc1单词记录下pos1文档记录下位置索引的数目
int possize2=Invertedfile[loc2][pos2][1];//此处pos1和pos2可能是不同的
while(pp1<possize1+2){
while(pp2<possize2+2){
if(isabs==0&&x<0){//str1在str2左边
if(Invertedfile[loc1][pos1][pp1]-Invertedfile[loc2][pos2][pp2]>=x){//判断邻近条件
ans a1(Invertedfile[loc1][pos1][0],Invertedfile[loc1][pos1][pp1],Invertedfile[loc2][pos2][pp2]);
answer.push_back(a1);//创建三元组:文档ID,词项在p1位置,词项在p2位置,将三元组添加入答案
}
else if(Invertedfile[loc1][pos1][pp1]>Invertedfile[loc2][pos2][pp2])//判断退出条件
break;
}
else if(isabs==0&&x>0){//str1在str2右边
if(Invertedfile[loc1][pos1][pp1]-Invertedfile[loc2][pos2][pp2]<=x){//判断邻近条件
ans a1(Invertedfile[loc1][pos1][0],Invertedfile[loc1][pos1][pp1],Invertedfile[loc2][pos2][pp2]);
answer.push_back(a1);
}
else if(Invertedfile[loc1][pos1][pp1]>Invertedfile[loc2][pos2][pp2]+x)//判断退出条件
break;
}
else if(isabs==1){//str1在str2附近
if(abs(Invertedfile[loc1][pos1][pp1]-Invertedfile[loc2][pos2][pp2])<=x){//判断邻近条件
ans a1(Invertedfile[loc1][pos1][0],Invertedfile[loc1][pos1][pp1],Invertedfile[loc2][pos2][pp2]);
answer.push_back(a1);
}
else if(Invertedfile[loc1][pos1][pp1]>Invertedfile[loc2][pos2][pp2]+x)//判断退出条件
break;
}
pp2++;
}
pp1++;
}
pos1++,pos2++;
}
else{
if(Invertedfile[loc1][pos1][0]<Invertedfile[loc2][pos2][0])
pos1++;
else
pos2++;
}
}
if(answer.size()>0){
cout<<"检索结果为:"<<endl;
for(int i=0;i<answer.size();i++)//输出三元组
answer[i].show();
}
else
cout<<"无符合要求文档!"<<endl;
}
int main(){
fstream HWfile( "e:\\学习\\HW2.txt", ios::in|ios::out );//打开目标文件
if(HWfile)
cout<<"目标文件打开成功"<<endl;
else
cout<<"目标文件打开失败"<<endl;
string line;
int row=0;//行,表示document的ID
int curpos;//位置,表示document中词项的position
int len=0;//表示单词词典长度
vector <string> Lexicon;//单词词典
int Invertedfile[148][50][5];
for(int i=0;i<148;i++)
for(int j=0;j<50;j++)
for(int k=0;k<5;k++)
Invertedfile[i][j][k]=0;//倒排文件初始化
/*
三维数组嵌套:
Invertedfile:倒排文件
Invertedfile[]:每个词项的倒排列表
Invertedfile[][0][0]:存储词项的document记录数
Invertedfile[][0][其他]:废弃
Invertedfile[][1~][0]:存储词项document记录的编号
Invertedfile[][1~][1]:存储词项document记录下位置索引的数量
Invertedfile[][1~][2~]:存储词项在ID为Invertedfile[][1~][0]的document中位置数据
*/
string word;//当前单词
while(getline(HWfile,line)){//整行读入一行(一个文件)的全部内容
curpos=1;//更新词项位置
for(int i=0;i<line.size();i++){//格式化文本文档
if(line[i]>='A'&&line[i]<='Z')//大小写转换
line[i]=line[i]+'a'-'A';
if((line[i]<'a'||line[i]>'z')&&line[i]!='-')
line[i]=' ';//清理其他符号并保留连字符
}
stringstream linestream(line);//将读入的每行文本转化为串流,用于切分单词
while(linestream>>word){//从第row行中读入一个单词,判断它是否在词典中
int pos=findword(Lexicon,word);//pos为这个单词在单词词典中的序号
if(pos!=-1){//词典中有这个单词
int flag=0;
for(int i=1;i<Invertedfile[pos][0][0];i++){
if(Invertedfile[pos][i][0]==row+1){
flag=i;
break;
}
}//查找这个单词的记录中是否有当前document,如果是,flag返回文件记录下标
if(flag!=0){//这个单词的倒排列表中有这个文件,则添加位置索引
int tempnum=Invertedfile[pos][flag][1];//当前词项当前文本记录中位置索引的数量
Invertedfile[pos][flag][tempnum+2]=curpos;
Invertedfile[pos][flag][1]++;//位置索引数量+1
}
else{//没有这个文件,则添加document并添加位置索引
Invertedfile[pos][0][0]++;//文本记录数量+1
int tempnum=Invertedfile[pos][0][0];//当前词项存储的文本记录数量
Invertedfile[pos][tempnum][0]=row+1;//存储DOCID
Invertedfile[pos][tempnum][1]=1;//当前文本位置索引数量+1
Invertedfile[pos][tempnum][2]=curpos;//存储位置索引
}
}
else{//词典中没有这个单词
Lexicon.push_back(word);//把单词添加到词典,这个单词在词典中的下标为len
Invertedfile[len][0][0]=1;
Invertedfile[len][1][0]=row+1;//在这个单词的倒排文件中添加当前document的序号
Invertedfile[len][1][1]=1;
Invertedfile[len][1][2]=curpos;
len++;//词典长度扩充
}
curpos++;
}
row++;//文件编号从1开始到60,因此文件编号为row+1
}
/*
for(int i=0;i<len;i++){//输出词典及倒排文件
cout<<Lexicon[i]<<" ";
int len0=Invertedfile[i][0][0];//获取当前单词倒排列表长度
for(int j=1;j<=len0;j++)
cout<<Invertedfile[i][j][0]<<" ";
cout<<endl;
}
*/
/*
for(int i=0;i<len;i++){//输出词典及位置索引文件
cout<<Lexicon[i]<<endl;
int len0=Invertedfile[i][0][0];//获取当前单词倒排列表长度
for(int j=1;j<=len0;j++){
cout<<"文档"<<Invertedfile[i][j][0]<<":";
int temppos=Invertedfile[i][j][1];
for(int k=2;k<2+temppos;k++)
cout<<" "<<Invertedfile[i][j][k];
cout<<endl;
}
cout<<endl;
}
cout<<"词典长度:"<<len<<endl;
*/
//↑实现倒排索引
//↓邻近搜索
searchpos(Lexicon,Invertedfile,"ranking","filtering",-4,0);//例子1
searchpos(Lexicon,Invertedfile,"ranking","filtering",-5,0);
searchpos(Lexicon,Invertedfile,"ranking","filtering",-6,0);
searchpos(Lexicon,Invertedfile,"ranking","filtering",-7,0);//例子4
searchpos(Lexicon,Invertedfile,"heterogeneous","learning",2,0);
searchpos(Lexicon,Invertedfile,"recommendation","bias",2,1);//表示左或者右均可
return 0;
}
例子1 (ranking, filtering, -4)上的运行结果截图和详细的文字说明:
本实验中距离理解为“ranking在filtering左侧距离4或4以内”。其他例子类似理解。因此搜索结果返回两词距离正好等于4的两个文档。
三元组为<文档ID,第一个词在文档中的位置,第二个词在文档中的位置>
例子2 (ranking, filtering, -5)上的运行结果截图和文字说明:
程序成功完成任务。
例子3 (ranking, filtering, -6)上的运行结果截图和文字说明:
程序成功完成任务。
例子4 (ranking, filtering, -7)上的运行结果截图和文字说明:
程序成功完成任务。
注意到随着位置索引搜索范围的逐渐扩大,返回的三元组越来越多。
例子5 (heterogeneous, learning, +2)上的运行结果截图和文字说明:
返回了5个三元组。
例子6 (recommendation, bias, 2)上的运行结果截图和文字说明:
程序成功完成任务。以上六个例子经过人工检验,输出结果无误。
(5). 阅读教材《Introduction to Information Retrieval》第59页Figure 3.5中所描述的基于动态规划(dynamic programming)来计算两个字符串的编辑距离(edit distance)的算法,并用Java语言或其他常用语言实现该算法。要求计算以下15组单词的编辑距离:
(business, buisness)
(committee, commitee)
(conscious, concious)
(definitely, definately)
(fluorescent, florescent)
(forty, fourty)
(government, goverment)
(idiosyncrasy, idiosyncracy)
(immediately, immediatly)
(millennium, millenium)
(noticeable, noticable)
(tendency, tendancy)
(truly, truely)
(weird, wierd)
(privilege, privledge)
代码截图和详细的文字说明:
参考以下的伪代码,实现计算编辑距离的算法。
算法步骤为:
- 初始化一个矩阵(len1,len2),len1和len2分别为两个输入字符串的长度。
- 初始化矩阵边缘的元素,用行和列值分别填充
- 从左上角向右下角填充矩阵,定义单步增删改的编辑距离为1。若字符串当前字符匹配,则该位置左侧元素+1,上方元素+1,左上方元素+0,三个数取最小的填充当前元素;如果字符串当前字符不匹配,则该位置左侧,上方,左上方三个位置的值中取最小值+1填充当前元素。
- 矩阵右下角(len1,len2)处的数值即为编辑距离。
具体实现代码为:
#include <bits/stdc++.h>
using namespace std;
int min_of3(int a,int b,int c){//三个数取最小
if(a<b&&a<c)
return a;
if(b<a&&b<c)
return b;
return c;
}
int main(){
int i,j,t;
string str1,str2;
cin>>t;//测试数组组数
while(t--){
cout<<"输入待判断的单词:";
cin>>str1>>str2;
int len1=str1.size(),len2=str2.size();//str1和str2的长度
int m[len1+1][len2+1];
for(i=0;i<=len1;i++)
m[i][0]=i;
for(j=1;j<=len2;j++)
m[0][j]=j;
//初始化矩阵
for(i=1;i<=len1;i++){
for(j=1;j<=len2;j++)
if(str1[i-1]==str2[j-1])//当前字符相等
m[i][j]=min_of3(m[i][j-1]+1,m[i-1][j]+1,m[i-1][j-1]);
else//当前字符不相等
m[i][j]=min_of3(m[i][j-1],m[i-1][j],m[i-1][j-1])+1;//取上一步最小值+1(改)
}
cout<<"编辑距离为:"<<m[len1][len2]<<endl;
}
}
运行结果截图和文字说明:
所有编辑距离均已被求出。手算之后验证答案正确。
计算得到的15组单词的编辑距离如下:
(business, buisness):2
(committee, commitee):1
(conscious, concious):1
(definitely, definately):1
(fluorescent, florescent):1
(forty, fourty):1
(government, goverment):1
(idiosyncrasy, idiosyncracy):1
(immediately, immediatly):1
(millennium, millenium):1
(noticeable, noticable):1
(tendency, tendancy):1
(truly, truely):1
(weird, wierd):2
(privilege, privledge):2
其他(例如感想、建议等等)。
通过这个实验,我学习了基于跳表指针的倒排索引表合并算法、位置索引表的邻近搜索算法和计算编辑距离的算法。这些算法更加贴近真实应用情景,帮助我们处理复杂而容易出错的用户实际往搜索框中输入的内容。
算法应该给用户留有充足的试错空间,不能对用户太过严苛。为了让用户能够有更好的搜索体验,我们只能进一步改进优化自己的算法。
通过这个实验我感受到内存管理的重要性。Vector容器的嵌套带来初始化的困难,使得变长容器申请太多内存而使内存崩溃。而如果把容器长度直接初始化,则失去了vector容器长度可变的优势。幸好本次实验数据量有限,因此直接使用int三维数组来存放数据,并且预留专门的数组位置来存放document记录数、位置索引记录数和具体的位置索引记录。但是在应对更大规模的数据时,这么做肯定是不够的。所以本实验代码可以日后继续改进。