简易倒排索引

本文介绍了一种使用C++实现的倒排索引结构,该结构能够从多个文件中提取词汇并建立词汇到文件位置的映射,实现高效的词汇查询功能。文章详细展示了倒排索引的初始化过程,包括读取文件、解析单词和更新索引,以及如何通过查询接口获取特定单词在各文件中的出现频率和位置。
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <unordered_map>
struct word_info {
    size_t frequency = 0;
    std::vector<size_t>pos;
};
class inverted_index_map{
public:
    using file_word_info_map = std::unordered_map<std::string, word_info>;  // key -- file path
    using index_map = std::unordered_map<std::string, file_word_info_map>;  // key -- word
public:
    inverted_index_map() = default;
    inverted_index_map(inverted_index_map &) = delete;
    inverted_index_map & operator = (inverted_index_map &) = delete;
    virtual ~inverted_index_map() = default;
public:
    void init(const std::vector<std::string>&files) {
        size_t pos = 0;
        std::string word;
        std::fstream fin;
        for (auto &filepath : files) {
            fin.open(filepath, std::ios::in);
            if (false == fin.is_open()) {
                std::cerr << filepath << " open failed." << std::endl;
                fin.close();
                continue;
            }
            while (fin >> word) {
                std::cout << "word = " << word << std::endl;
                index_map_[word][filepath].frequency++;
                index_map_[word][filepath].pos.emplace_back(pos++);
            }
            fin.close();
        }
    }
    bool query(std::string &word) {
        auto it = index_map_.find(word);
        if (it == end(index_map_)) {
            return false;
        }
        auto &file_word_info_map_ = it->second;
        for (auto &file_word_info : file_word_info_map_) {
            std::cout << file_word_info.first << std::endl;
            std::cout << file_word_info.second.frequency << std::endl;
            for (auto &pos : file_word_info.second.pos) {
                std::cout << pos << std::endl;
            }
            std::cout << file_word_info.second.frequency << std::endl;
        }
        return true;
    }
private:
    index_map index_map_;
};
int main() {
    std::vector<std::string>files{"./test.cpp"};
    inverted_index_map mm;
    mm.init(files);
    std::string word = "return";
    std::cout << mm.query(word) << std::endl;

    return 0;
}

 

倒排索引(Inverted Index)是信息检索系统中的核心技术之一,广泛应用于搜索引擎、全文检索系统(如 Elasticsearch、Lucene)中。它的主要作用是**快速查找包含某个关键词的文档列表**。 --- ### 一、什么是倒排索引? 在传统的正向索引(Forward Index)中,我们记录的是: **文档 → 包含的词项(words)** 例如: | 文档ID | 内容 | |--------|--------------------------| | 1 | "java is great" | | 2 | "java and python are cool" | | 3 | "python is powerful" | 对应的正向索引为: - Doc 1: [java, is, great] - Doc 2: [java, and, python, are, cool] - Doc 3: [python, is, powerful] 而**倒排索引**则是反过来建立映射关系: **词项 → 包含该词项的文档列表** 构建后的倒排索引如下: ``` java → [1, 2] is → [1, 3] great → [1] and → [2] python → [2, 3] are → [2] cool → [2] powerful→ [3] ``` 这个结构称为 **倒排索引表(Inverted Index Table)**,其中每一项叫做一个 **倒排项(Posting)**。 --- ### 二、倒排索引的核心组成 一个完整的倒排索引通常包括以下部分: 1. **词汇表(Term Dictionary)** 所有唯一词项的集合,通常用哈希表或 Trie 树存储,用于快速查找词项是否存在。 2. **倒排列表(Posting List)** 每个词项对应一个文档 ID 列表,表示哪些文档包含了这个词。 3. **附加信息(可选但重要)** - 词频(TF, Term Frequency):词在文档中出现的次数 - 位置信息(Position):词在文档中的偏移量,支持短语查询(如 "java programming") - 字段权重、长度归一化因子等(用于打分排序) 示例增强版倒排索引(带位置和词频): ```text java → Doc 1: freq=1, positions=[0] Doc 2: freq=1, positions=[0] python → Doc 2: freq=1, positions=[2] Doc 3: freq=1, positions=[0] ``` --- ### 三、倒排索引的工作流程 1. **文本预处理**: - 分词(Tokenization):将文本拆分为单词 - 转小写(Lowercasing) - 去停用词(Stop Words Removal,如 "the", "and") - 词干提取(Stemming/Lemmatization),如 "running" → "run" 2. **构建索引**: 遍历所有文档,对每个词项记录其出现的文档及位置。 3. **查询处理**: 用户输入关键词后,从词汇表中查找对应词项,获取其倒排列表,进行合并操作(如 AND、OR 查询)。 --- ### 四、Java 实现简易倒排索引 下面是一个简单的 Java 示例,展示如何构建基本的倒排索引: ```java import java.util.*; public class InvertedIndex { // 倒排索引:term -> list of doc IDs private Map<String, Set<Integer>> index = new HashMap<>(); // 存储文档内容(便于调试) private Map<Integer, String> documents = new HashMap<>(); /** * 添加文档到索引 * * @param docId 文档唯一标识 * @param text 文档内容 */ public void addDocument(int docId, String text) { documents.put(docId, text); // 简单分词:按空格分割并转小写 String[] tokens = text.toLowerCase().split("\\W+"); for (String token : tokens) { if (token.isEmpty()) continue; index.computeIfAbsent(token, k -> new HashSet<>()).add(docId); } } /** * 搜索包含关键词的文档 * * @param term 关键词 * @return 包含该词的文档 ID 列表 */ public List<Integer> search(String term) { Set<Integer> result = index.getOrDefault(term.toLowerCase(), new HashSet<>()); List<Integer> sorted = new ArrayList<>(result); Collections.sort(sorted); return sorted; } /** * 多词查询:AND 操作 */ public List<Integer> searchAnd(List<String> terms) { if (terms.isEmpty()) return new ArrayList<>(); Set<Integer> result = new HashSet<>(index.getOrDefault(terms.get(0).toLowerCase(), new HashSet<>())); for (int i = 1; i < terms.size(); i++) { result.retainAll(index.getOrDefault(terms.get(i).toLowerCase(), new HashSet<>())); } List<Integer> sorted = new ArrayList<>(result); Collections.sort(sorted); return sorted; } // 测试方法 public static void main(String[] args) { InvertedIndex idx = new InvertedIndex(); idx.addDocument(1, "Java is great"); idx.addDocument(2, "Java and Python are cool"); idx.addDocument(3, "Python is powerful"); System.out.println("Documents containing 'java': " + idx.search("java")); // [1, 2] System.out.println("Documents containing 'python': " + idx.search("Python")); // [2, 3] System.out.println("Documents containing 'java' AND 'and': " + idx.searchAnd(Arrays.asList("java", "and"))); // [2] } } ``` --- ### 五、倒排索引的优点与挑战 #### ✅ 优点: - **高效查询**:O(1) 查找词项,O(k) 获取文档列表(k 是匹配文档数) - **支持复杂查询**:布尔查询(AND/OR/NOT)、短语查询、模糊查询等 - **易于扩展**:可结合 TF-IDF、BM25 等算法做相关性排序 #### ❌ 挑战: - **内存占用大**:海量文档时,索引可能达到 GB/TB 级别 - **更新成本高**:插入/删除文档需动态维护索引(现代系统使用 LSM-Tree 或段合并机制解决) - **分词质量影响效果**:中文需特殊分词器(如 IK Analyzer) --- ### 六、实际应用中的优化技术 1. **压缩倒排列表**:使用 VarByte、PForDelta 等算法压缩 DocID 差值(Δ-encoded) 2. **索引分段(Segment-based indexing)**:新数据写入新段,定期合并(Lucene 使用此策略) 3. **布隆过滤器(Bloom Filter)**:快速判断某 term 是否存在,避免无效查表 4. **分布式索引**:数据分片(Sharding)存储在多个节点上(如 Elasticsearch) --- ### 总结 倒排索引通过“词项 → 文档”的反向映射机制,极大提升了关键词检索效率,是搜索引擎的基石。虽然简单概念容易理解,但在大规模场景下涉及诸多工程优化技巧。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值