谷歌原来是这么做的!

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

其核心目的是将从大量文档中查找包含某些词的文档集合这一任务用 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()
相关推荐
brzhang1 小时前
我操,终于有人把 AI 大佬们 PUA 程序员的套路给讲明白了!
前端·后端·架构
珊瑚里的鱼2 小时前
LeetCode 692题解 | 前K个高频单词
开发语言·c++·算法·leetcode·职场和发展·学习方法
wan_da_ren3 小时前
JVM监控及诊断工具-GUI篇
java·开发语言·jvm·后端
秋说3 小时前
【PTA数据结构 | C语言版】顺序队列的3个操作
c语言·数据结构·算法
【本人】3 小时前
Django基础(一)———创建与启动
后端·python·django
lifallen4 小时前
Kafka 时间轮深度解析:如何O(1)处理定时任务
java·数据结构·分布式·后端·算法·kafka
liupenglove4 小时前
自动驾驶数据仓库:时间片合并算法。
大数据·数据仓库·算法·elasticsearch·自动驾驶
你的人类朋友4 小时前
【✈️速通】什么是SIT,什么是UAT?
后端·单元测试·测试
python_tty5 小时前
排序算法(二):插入排序
算法·排序算法
然我5 小时前
面试官:如何判断元素是否出现过?我:三种哈希方法任你选
前端·javascript·算法