倒排索引詳解及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;
}
輸出:
參考文獻
(1)《計算廣告》