位置信息倒排索引K词近邻搜索算法实现

本文介绍了位置信息倒排索引的概念,它在倒排记录中添加词项的位置信息,便于扩展搜索功能。具体讨论了基于此的K词近邻搜索算法,该算法寻找两个词项在文档中间隔为k的匹配。以查询'to be or not to be'为例,展示了如何查找相邻词项。算法实现采用Java,来源于《信息检索导论》并附有代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

位置信息索引是在倒排索引的基础上实现的,在倒排记录表中添加了词项在文档中的位置信息。位置信息一般以下面的方式存储到倒排记录中:

文档ID:(位置1,位置2,…)
而完整的包含位置信息的倒排记录表如下图所示:

图中一个倒排记录为例,to是此项,993427为to的文档频率,即在993427篇文档中出现。最外层的括号中,1,2,4,5,7是包含to的文档ID,这里只列出5个,文档ID后是to的词频,即to在文档中出现的次数。最后内层的括号中即to在文档中的位置信息,以对文档开头的偏移量来表示。有了这“强化”过后的倒排索引表,我们可以对倒排索引查询的功能进行扩展。而这次我带来的是基于位置信息的倒排索引K词近邻搜索算法的实现,具体的含义就是对两个词项进行搜索,可规定两个词项在文档中的间隔。

举个例子:某个查询为to be or not to be,我们只看to be,to与be是挨着的,也就是说间隔k为0个词。首先,查找同时包含这两个词项的文档;然后,检查表中的位置看看是否有某个be的前面一个词条位置上正好出现to。下面中给出的倒排记录表中,可能存在的一个匹配为:

to: 〈. . . ; 4: 〈. . . ,429,433〉; . . . 〉
	be: 〈. . . ; 4: 〈 . . ,430,434〉; . . . 〉

本文中给出的算法来源于《信息检索导论》一书中,王斌翻译,书中给出了算法伪代码。另外我的算法实现是在课程结束之后完成的,JAVA写的,只是很单纯的实现,代码写得也比较差劲,不值得拿来做研究使用,只是贴出来给大家进行参考。下面给出代码和索引文件。

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import javax.annotation.PostConstruct;


public class PositionalIndex {
	
	
	public PositionalIndex() {
		// TODO Auto-generated constructor stub
		//LTerm = new ArrayList<String>();
		//LPostingList = new ArrayList<String>();
	}
	
	@SuppressWarnings("resource")
	/******************** 按词条搜索出对应的倒排记录******************/
	public static String findPostingList(String term)
	{
		List<String> LTerm = new ArrayList<>();
		String szPostingList = "";
		File file = new File("bin/Index.txt");				//读出倒排记录表文件
		String szTermAndPosting;
		String szSub;
		//StringTokenizer szToken = null;
		BufferedReader br =null;
		int iSplitIndex;
		try
		{
			br = new BufferedReader(new FileReader(file));
			while((szTermAndPosting = br.readLine())!=null)				//按行读取出一条倒排记录
			{
				iSplitIndex = szTermAndPosting.indexOf(",");			//按','划分倒排记录
				szSub = szTermAndPosting.substring(0, iSplitIndex);		//得到词项
				szSub.trim();
				if(term.equals(szSub))									//判断搜索词条与词项是否匹配
				{
					LTerm.add(term);
					iSplitIndex = szTermAndPosting.indexOf(":");		//划分文档频率与倒排记录
					szSub = szTermAndPosting.substring(iSplitIndex+1);	//得到倒排记录
					
					//szSub = szSub.substring(iSplitIndex+1);
					szPostingList += szSub;
					break;
				}
			}
			br.close();
		}
		catch(Exception e){}
		
		return szPostingList;											//返回倒排记录
	}
	
	/***********************根据k值来搜索出倒排记录表中间隔小于k的文档列表**************************/
	public static List<String> PosIntersect(String termAndpList_1,String termAndpList_2,int k)
	{
		List<String> LAns = new ArrayList<>();
		StringTokenizer strToken;
		List<String> szTerm_1 = new ArrayList<>(),szTerm_2 = new ArrayList<>();
		List<String> szPList_1 = new ArrayList<>(),szPList_2 = new ArrayList<>();			//倒排记录
		int iDocID_1,iDocID_2;
		List<String> LtermAndList_1 = new ArrayList<>(),LtermAndList_2 = new ArrayList<>();
		
		strToken = new StringTokenizer(termAndpList_1, ";");								//按';'划分倒排记录
		while(strToken.hasMoreTokens())
		{
			LtermAndList_1.add(strToken.nextToken());										//文档id+位置信息
		}
		
		strToken = new StringTokenizer(termAndpList_2, ";");
		while(strToken.hasMoreTokens())
		{
			LtermAndList_2.add(strToken.nextToken());
		}
		
		for(int i = 0,j = 0;i<LtermAndList_1.size()&&j<LtermAndList_2.size();)
		{
			strToken = new StringTokenizer(LtermAndList_1.get(i)," ");						//划分文档id+位置信息
			szTerm_1.add(strToken.nextToken());
			szPList_1.add(strToken.nextToken());											//位置信息
			
			strToken = new StringTokenizer(LtermAndList_2.get(j), " ");
			szTerm_2.add(strToken.nextToken());
			szPList_2.add(strToken.nextToken());
			
			int iSplitIndex = szTerm_1.get(i).indexOf(",");
			iDocID_1 = Integer.parseInt(szTerm_1.get(i).substring(0, iSplitIndex));			//得到文档id
			iSplitIndex = szTerm_2.get(i).indexOf(",");
			iDocID_2 = Integer.parseInt(szTerm_2.get(j).substring(0, iSplitIndex));
			
			/******************两个倒排记录根据文档id号进行比较*********************/
			if(iDocID_1 == iDocID_2)														//判断倒排记录中相同的文档
			{
				List<String> Ltemp = new ArrayList<>();
				List<String> Lposting_1 = new ArrayList<>();
				List<String> Lposting_2 = new ArrayList<>();
				StringTokenizer szPostingToken = new StringTokenizer(szPList_1.get(i),",");		//划分位置信息
				while(szPostingToken.hasMoreTokens())
				{
					Lposting_1.add(szPostingToken.nextToken());
				}
				szPostingToken = new StringTokenizer(szPList_2.get(j),",");
				while(szPostingToken.hasMoreTokens())
				{
					Lposting_2.add(szPostingToken.nextToken());
				}
				for(int p=0;p<Lposting_1.size();p++)
				{
					for(int q=0;q<Lposting_2.size();q++)
					{
						if(Math.abs(Integer.parseInt(Lposting_1.get(p))-Integer.parseInt(Lposting_2.get(q)))<=k)	//计算文档中词项之间的距离与k进行比较
						{
							Ltemp.add(Lposting_2.get(q));				//将与Lposting_1中位置相比符合条件的Lposting_2的位置信息进行存储
						}
						else if(Integer.parseInt(Lposting_2.get(q))>Integer.parseInt(Lposting_1.get(p)))
								break;
					}
					/*for(int x=0;x<Ltemp.size()&&Math.abs(Integer.parseInt(Ltemp.get(x))-Integer.parseInt(Lposting_1.get(p)))>k;x++)
					{
						Ltemp.remove(0);
					}*/
					for(int x=0;x<Ltemp.size();x++)
					{
						String ansTemp = Integer.toString(iDocID_1)+","+Lposting_1.get(p)+","+Ltemp.get(x);			//将与Lposting_1比较符合条件的Ltemp中的位置信息组合成字符串进行存储
						LAns.add(ansTemp);
					}
					Ltemp.clear();
				}
				i++;j++;
				
			}
			else if(iDocID_1<iDocID_2)					//iDocID_1<iDocID_2,IDocID_1的序号+1
				i++;
			else j++;									//同理,IDocID_2的序号+1,返回继续比较
		}
		
		return LAns;
		
	}
	/**
	 * @param args
	 * @throws IOException 
	 */
	public static void main(String[] args){
		// TODO Auto-generated method stub
		String sztermAndpList_1 ;		//词条与倒排记录
		String sztermAndpList_2 ;
		String szReadline;
		String szTerm_1;				//词条
		String szTerm_2;
		int k;
		BufferedReader Stdin = new BufferedReader(new InputStreamReader(System.in));	
		try
		{
			while((szReadline = Stdin.readLine())!=null&&!szReadline.equals("exit"))	//控制台输入,exit退出
			{
				List<String> LAnswer = new ArrayList<>();
				StringTokenizer szArgsToken = new StringTokenizer(szReadline," ");		//按空格划分出词条
				szTerm_1 = szArgsToken.nextToken();
				szTerm_2 = szArgsToken.nextToken();
				k = Integer.parseInt(szArgsToken.nextToken());							//划分出k值
				sztermAndpList_1 = findPostingList(szTerm_1);							//按词条得出词项倒排记录组
				sztermAndpList_2 = findPostingList(szTerm_2);
				LAnswer = PosIntersect(sztermAndpList_1, sztermAndpList_2, k);			//存储结果
				if(LAnswer.size()==0)													//输出
					System.out.println("not found...");
				for(int i=0;i<LAnswer.size();i++)
					System.out.println(LAnswer.get(i));
			}
			Stdin.close();
		}
		catch(IOException e){}
	}
}

下面是索引文件Index.txt截图,倒排记录间隔的符号是规定好的,方便程序中对各部分的划分。索引是手动构建的,并非使用程序构建。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值