倒排索引是现代搜索引擎的核心技术。
其核心目的是将从大量文档中查找包含某些词的文档集合这一任务用 O(1) 或 O(log n) 的时间复杂度完成,其中 n 为索引中的文档数目。
也就是说,倒排索引可以实现搜索复杂度和文档集大小的去相关性,这在需要从大量文档中寻找某个关键词的搜索场景中显得尤为重要。
那么,倒排索引究竟是什么呢?
正排索引
首先,有倒排肯定就有正排。
正排索引其实就是按照顺序把文档储存起来,可以通过索引ID快速获取到文档的内容。
比如下面这个数组:
python
docs = [
'苹果发布新款iPhone手机',
'苹果发布iPhone 13系列手机',
'苹果发布iPhone 15 Pro Max',
'苹果推出新一代iOS操作系统',
'苹果公布2023年财年季度财报',
'苹果发布全新M3芯片的MacBook Pro'
]
可以很简单通过某个索引ID来获取对应的文档,比如 doc[1] 对应的文档为 "苹果发布新款iPhone手机"。
不难看出,正排索引的特点是十分直观且简单,并且由于文档直接存储在索引中,所以访问速度也很快。
但是如果现在我要求根据某个关键词比如"手机",来查出对应的文档,那么如果是用正排索引,就不得不遍历整个文档集了。
这个时候的时间复杂度可想而知是很大的,同时跟文档集大小以及文档的大小有关(文档字数越多,遍历起来越慢),也就是 O(n * m),其中n是文档集大小,m是最大的文档大小。
而倒排索引可以很好地解决这个问题。
倒排索引
倒排索引之所以叫倒排,是因为传统的正排索引是一种 ID → doc 的方式,而倒排索引则恰好倒过来,是 doc → ID 的方式,根据具体的内容来映射文档的ID。
比如上面的例子可以变为:
python
inverted_index = {
'苹果': [0, 1, 2, 3, 4, 5],
'发布': [0, 1, 2, 5],
'手机': [0, 1],
...
}
这样,当我想要搜索关键词"手机"的时候,可以用 O(1) 的时间复杂度得到 [0, 1],然后用 0 和 1 从正排索引中找到对应的文档内容就可以快速地找出我想要的文档了。
实现一个简单的倒排索引
这里我们用Python来实现一个简单的倒排索引。
首先,肯定包括两个部分:
- 构建索引
- 检索数据
首先还是采用上面的docs作为正排索引。
构建索引这块我们使用jieba对文档进行分词:
python
def build_inverted_index(docs: str) -> dict[id]:
inverted_index = defaultdict(list)
for doc in docs:
words = jieba.cut_for_search(doc)
for word in words:
inverted_index[word].append(doc)
return inverted_index
然后就是对构建好的倒排索引进行检索:
python
def retrieve(query: str, inverted_index: dict[id]) -> list[str]:
words = jieba.cut_for_search(query)
results = set()
for word in words:
if word in inverted_index:
results.update(inverted_index[word])
return list(results)
简单写一个交互并测试效果:
python
inverted_index = build_inverted_index(forward_index)
while True:
query = input("请输入查询词:")
if query == "q":
break
results = retrieve(query, inverted_index)
print("查询结果:")
if results is None:
print("没有找到匹配结果")
continue
for doc in results:
print(doc)
print()
最终的测试结果为:

完整代码
python
import jieba
from collections import defaultdict
forward_index = [
"苹果发布新款iPhone手机",
"苹果发布iPhone 13系列手机",
"苹果发布iPhone 15 Pro Max",
"苹果推出新一代iOS操作系统",
"苹果公布2023年财年季度财报",
"苹果发布全新M3芯片的MacBook Pro",
]
def build_inverted_index(docs: str) -> dict[id]:
inverted_index = defaultdict(list)
for doc in docs:
words = jieba.cut_for_search(doc)
for word in words:
inverted_index[word].append(doc)
return inverted_index
def retrieve(query: str, inverted_index: dict[id]) -> list[str]:
words = jieba.cut_for_search(query)
results = set()
for word in words:
if word in inverted_index:
results.update(inverted_index[word])
return list(results)
inverted_index = build_inverted_index(forward_index)
while True:
query = input("请输入查询词:")
if query == "q":
break
results = retrieve(query, inverted_index)
print("查询结果:")
if results is None:
print("没有找到匹配结果")
continue
for doc in results:
print(doc)
print()