谷歌原来是这么做的!

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

其核心目的是将从大量文档中查找包含某些词的文档集合这一任务用 O(1) 或 O(log n) 的时间复杂度完成,其中 n 为索引中的文档数目。

也就是说,倒排索引可以实现搜索复杂度和文档集大小的去相关性,这在需要从大量文档中寻找某个关键词的搜索场景中显得尤为重要。

那么,倒排索引究竟是什么呢?

正排索引

首先,有倒排肯定就有正排。

正排索引其实就是按照顺序把文档储存起来,可以通过索引ID快速获取到文档的内容。

比如下面这个数组:

python 复制代码
docs = [  
    '苹果发布新款iPhone手机',  
    '苹果发布iPhone 13系列手机',  
    '苹果发布iPhone 15 Pro Max',  
    '苹果推出新一代iOS操作系统',  
    '苹果公布2023年财年季度财报',  
    '苹果发布全新M3芯片的MacBook Pro'
]

可以很简单通过某个索引ID来获取对应的文档,比如 doc1 对应的文档为 "苹果发布新款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()
相关推荐
血小板要健康29 分钟前
二叉树 dfs 题型总结
算法·深度优先
我变成萤火虫6 小时前
河南萌新联赛2026第(四)场:南阳理工学院
数据结构·c++·算法·贪心算法·stl·动态规划
To_OC8 小时前
LC 239 滑动窗口最大值:从暴力超时到单调队列一遍过
javascript·算法·leetcode
To_OC8 小时前
从一行入口代码啃透 NestJS:工厂模式、装饰器和依赖注入是怎么串起来的
后端·设计模式·nestjs
Python私教8 小时前
多个项目怎么安全合并?先适配,再切换
后端·python·架构
.道阻且长.9 小时前
9.LeetCode算法习题讲解--滑动窗口--无重复字符的最长字串
算法·leetcode·职场和发展·哈希算法
Python私教10 小时前
从表格到管理系统:别先写页面,先补齐权限、流程和审计
数据库·后端·架构
ltl10 小时前
HNSW:图索引如何击败树索引
算法
北斗落凡尘10 小时前
LangGraph 入门实战(12)--使用MCP
后端·python·langchain
曹牧12 小时前
C#:数字的定义和表示方式
算法·c#