倒排索引详解及C++实现

1.介绍

倒排索引是现代搜索引擎的核心技术之一,其核心目的是将从大量文档中查找包含某些词的文档集合这一任务用O(1)或O(logn)的时间复杂度完成,其中n为索引中的文档数目。也就是说,利用倒排索引技术,可以实现与文档集大小基本无关的检索复杂度,这一点对于海量内容的检索来说至关重要。

2.示例

假设我们有如下几篇文档:

D1 = “谷歌地图之父跳槽Facebook”
  D2 = “谷歌地图之父加盟Facebook”
  D3 = “谷歌地图创始人拉斯离开谷歌加盟Facebook”
  D4 = “谷歌地图创始人跳槽Facebook与Wave项目取消有关”
  D5 = “谷歌地图创始人拉斯加盟社交网站Facebook”

对每篇文档都进行分词以后,可以这些文档中包含的关键词有:{谷歌,地图,之父,跳槽,Facebook,加盟,创始人,拉斯,离开,与,Wave,项目,取消,有关,社交,网站}

首先,去掉“与”这样的没有实际表意作用的停用词。
  然后,对每一个词建立一个链表,表中的每个元素都是包含该词的某篇文档的标识。
  于是,得到如下的倒排链集合。

谷歌—{D1,D2,D3,D4,D5},地图—{D1,D2,D3,D4,D5},之父—{D1,D2},跳槽—{D1,D2},Facebook—{D1,D2,D3,D4,D5},创始人—{D3,D4,D5},加盟—{D2,D3,D5},拉斯—{D3,D5},离开—{D3},Wave—{D4},取消—{D4},项目—{D4},有关—{D4},社交—{D5},网站—{D5}
  
3.实现细节

我们使用一个类结构来描述一个倒排索引,这个类结构派生于hash map,其中的键为关键词。典型情况下,该键是string类型,但是也有可能发生变化,为了逻辑统一,引入了模板参数来泛化此处的数据类型。

倒排索引的基本操作有两项:一是想索引中加入一个新文档,而是给定一个由多个关键词组成的查询,返回对应的文档集合。

在倒排索引中,由于文档ID是在加入倒排索引是被在线分配的,因此每个倒排链都可以确保是有序的

4.实现代码

main函数中包含示例部分的测试

#include <iostream>
#include <map>
#include <list>
#include <vector>
#include <string>
#include <set>

using namespace std;

template <class TKey>
class InvIndex : public map<TKey, list<int>>
{
public:
	vector<vector<TKey>> docs; //文档正排表

public:
	//向索引中加入一个文档
	void add(vector<TKey>& doc)
	{
		//在正排表里记录该文档
		docs.push_back(doc);
		int curDocID = docs.size();  //现在代码:使得文档编号从1开始 原始代码:int curDocID = docs.size()-1;

		//遍历doc里所有的term
		for (int w = 0; w < doc.size(); w++)
		{
			map<TKey,list<int>>::iterator it;
			it = this->find(doc[w]);

			//如果该term的倒排链不存在,新建倒排链
			if (it == this->end())
			{
				list<int> newList;
				(*this)[doc[w]] = newList;
				it = this->find(doc[w]);
			}

			//在倒排链末尾插入新的文档
			it->second.push_back(curDocID);
		}
	}

	//在索引中进行一次查询
	void retrieve(vector<TKey>& query, set<int>& docIDs)
	{
		int termNum = query.size();

		//合并所有term的倒排链
		docIDs.clear();
		for (int t = 0; t < termNum; t++)
		{
			map<TKey,list<int>>::iterator it;
			//该term倒排链不存在则跳过
			if ((it = this->find(query[t])) != this->end())
				docIDs.insert(it->second.begin(),it->second.end());
		}

	}
};


int main()
{

	string D1_tmp[] = {"谷歌","地图","之父","跳槽","Facebook"};
	int D1_tmp_size = sizeof(D1_tmp)/sizeof(string);
	vector<string> D1(D1_tmp,D1_tmp+D1_tmp_size);

	string D2_tmp[] = {"谷歌","地图","之父","加盟","Facebook"};
	int D2_tmp_size = sizeof(D2_tmp)/sizeof(string);
	vector<string> D2(D2_tmp,D2_tmp+D2_tmp_size);

	string D3_tmp[] = {"谷歌","地图","创始人","拉斯","离开","谷歌","加盟","Facebook"};
	int D3_tmp_size = sizeof(D3_tmp)/sizeof(string);
	vector<string> D3(D3_tmp,D3_tmp+D3_tmp_size);

	string D4_tmp[] = {"谷歌","地图","创始人","跳槽","Facebook","与","Wave","项目","取消","有关"};
	int D4_tmp_size = sizeof(D4_tmp)/sizeof(string);
	vector<string> D4(D4_tmp,D4_tmp+D4_tmp_size);

	string D5_tmp[] = {"谷歌","地图","创始人","拉斯","加盟","社交","网站","Facebook"};
	int D5_tmp_size = sizeof(D5_tmp)/sizeof(string);
	vector<string> D5(D5_tmp,D5_tmp+D5_tmp_size);

	InvIndex<string>* inverted_index = new InvIndex<string>;
	inverted_index->add(D1);
	inverted_index->add(D2);
	inverted_index->add(D3);
	inverted_index->add(D4);
	inverted_index->add(D5);

	
	string str_query[] = {"谷歌","地图","之父","跳槽","Facebook","创始人","加盟","拉斯","离开","与","Wave","项目","取消","有关","社交","网站"};

	

	for(int i = 0; i < sizeof(str_query)/sizeof(string); i++)
	{
		vector<string> query;
		query.push_back(str_query[i]);

		cout << str_query[i] << " ";

		set<int> docSet;

		inverted_index->retrieve(query,docSet);

		set<int>::iterator it;
		for (it = docSet.begin(); it != docSet.end(); it++)
		{
			cout << "D" << *it << " ";
		}
		cout << endl;

	}

	return 0;
}

输出:

![](https://imgconvert.csdnimg.cn/aHR0cDovL2ltZy5ibG9nLmNzZG4ubmV0LzIwMTYxMjE3MTEzOTQ3MTQ0?x-oss-process=image/format,png)

参考文献
(1)《计算广告》

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值