谷歌原来是这么做的!

倒排索引是现代搜索引擎的核心技术。

其核心目的是将从大量文档中查找包含某些词的文档集合这一任务用 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来实现一个简单的倒排索引。

首先,肯定包括两个部分:

  1. 构建索引
  2. 检索数据

首先还是采用上面的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()
相关推荐
Goboy2 分钟前
入坑了,都是2e31惹得锅
后端
Java水解6 分钟前
深入剖析Spring IOC容器——原理、源码与实践全解析
后端·spring
绝无仅有7 分钟前
Go 语言常用命令使用与总结
后端·面试·github
Code_Artist12 分钟前
[Java并发编程]6.并发集合类:ConcurrentHashMap、CopyOnWriteArrayList
java·后端·源码阅读
j_xxx404_13 分钟前
数据结构:单链表的应用(力扣算法题)第一章
c语言·数据结构·算法·leetcode
码事漫谈15 分钟前
深入解析:为什么应该避免使用 atoi、atol 和 atof 函数
后端
码事漫谈24 分钟前
现代C++工具链实战:CMake + Conan + vcpkg依赖管理
后端
百度Geek说25 分钟前
ERNIE-4.5-VL:技术解密+应用实战,解锁多模态新场景!
算法
杨杨杨大侠1 小时前
第6篇:链路追踪系统 - 分布式环境下的请求跟踪
java·后端·apache log4j
花妖大人1 小时前
Python写成接口
后端