谷歌原来是这么做的!

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

其核心目的是将从大量文档中查找包含某些词的文档集合这一任务用 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()
相关推荐
MX_93594 分钟前
SpringMVC静态资源访问、annotation-driven的使用原理及数据响应模式
java·后端·spring
人间寥寥情难诉6 分钟前
LRU算法本地实现
java·算法·spring
moonsea02039 分钟前
2026.4.2
开发语言·c++·算法
cpp_250115 分钟前
P10376 [GESP202403 六级] 游戏
c++·算法·动态规划·题解·洛谷·gesp六级
智者知已应修善业17 分钟前
【51单片机4个IO实现16按键可扩展独立按键64矩阵驱动显示矩阵原值】2023-5-8
c++·经验分享·笔记·算法·51单片机
hui-梦苑17 分钟前
[GROMACS]模拟数据分析前轨迹文件生成-轨迹预处理
人工智能·算法·数据分析
无籽西瓜a20 分钟前
【西瓜带你学设计模式 | 第十二期 - 装饰器模式】装饰器模式 —— 动态叠加功能实现、优缺点与适用场景
java·后端·设计模式·软件工程·装饰器模式
蒸汽求职20 分钟前
低延迟系统优化:针对金融 IT 与高频交易,如何从 CPU 缓存行(Cache Line)对齐展现硬核工程底蕴?
sql·算法·缓存·面试·职场和发展·金融·架构
南山乐只22 分钟前
Java并发工具:synchronized演进,从JDK 1.6 锁升级到 JDK 24 重构
java·开发语言·后端·职场和发展
田梓燊23 分钟前
leetcode 239
数据结构·算法·leetcode