深圳大学信息检索:词典、倒排记录表和容错式检索的实验

实验目的:

掌握跳表指针(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的下一步将跳到skipp1))?

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 treadANDfools 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)

代码截图和详细的文字说明:

 参考以下的伪代码,实现计算编辑距离的算法。

算法步骤为:

  1. 初始化一个矩阵(len1,len2),len1和len2分别为两个输入字符串的长度。
  2. 初始化矩阵边缘的元素,用行和列值分别填充
  3. 从左上角向右下角填充矩阵,定义单步增删改的编辑距离为1。若字符串当前字符匹配,则该位置左侧元素+1,上方元素+1,左上方元素+0,三个数取最小的填充当前元素;如果字符串当前字符不匹配,则该位置左侧,上方,左上方三个位置的值中取最小值+1填充当前元素。
  4. 矩阵右下角(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记录数、位置索引记录数和具体的位置索引记录。但是在应对更大规模的数据时,这么做肯定是不够的。所以本实验代码可以日后继续改进。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值