当前位置: 首页 > article >正文

算法基础 -- Trie压缩树原理

Trie 压缩树原理

一、Trie 与压缩 Trie 的基本概念

1. 普通 Trie(字典树)

Trie(前缀树或字典树)是一种用于高效存储和检索字符串的树形数据结构。它的关键特性包括:

  1. 节点(Node)不直接存储完整字符串,而是根据字符串的前缀共用节点。
  2. 每条边代表可能是一个字符(在最常见的实现里,每一层对应一个字符),也可能是一个字符集的索引。

例如,将字符串集合 ["he", "hello", "hi"] 插入普通 Trie 时,结构如下:

        (root)
        /    \
      'h'    ...
       |
       v
      (node1)
     /      \
   'e'      'i'
    |        |
   (node2)  (node3)
   /   \
 'l'   (单词结束标记:he)
  |
 (node4)
  |
 'l'
  |
 (node5)
  |
 'o'
  |
 (node6) (单词结束标记:hello)

普通 Trie 的优点是结构清晰,查询和插入的时间复杂度理想情况下为 O(k),其中 k 是字符串长度。但其缺点是当字符串有较多相同前缀时,会出现大量“只有一个孩子的链状节点”,浪费了存储空间。

2. 压缩 Trie(Compressed Trie)

压缩 Trie 对普通 Trie 进行了优化,当一个节点只有唯一分支时,将这些连续的节点合并到一条边上,存储为一个更长的“公共子串”。这样可以显著减少冗余节点。

例如,字符串集合 ["he", "hello", "hi"] 的压缩 Trie:

        (root)
         |
        "h"
         |
       (节点A)
       /    \
    "e"     "i"
     |
   (节点B)
     |
   "llo"
     |
   (节点C) (单词结束标记:hello)
压缩 Trie 的操作要点
  • 插入

    1. 找到要插入字符串与现有边的公共前缀;
    2. 如果完全匹配,进入下一层节点继续处理;
    3. 如果部分匹配,需要拆分边,将公共前缀与剩余部分分别存储;
    4. 如果没有匹配,新建一条边。
  • 搜索:沿着边逐字符匹配。如果匹配完整且到达结束标记,则表示字符串存在;否则表示搜索失败。

这种方式能在空间和查询效率之间取得较好平衡。


二、C 语言实现

1. 数据结构定义

在实现中,我们定义以下结构体:

Edge 结构体

表示一条边,包含:

  • char *label:存储边上的字符串片段(子串)。
  • struct TrieNode *child:指向后续节点。
TrieNode 结构体

表示 Trie 中的一个节点,包含:

  • bool isEndOfWord:标记是否为单词结束节点。
  • int numChildren:子边的数量。
  • Edge *children:动态数组形式存储子边。

结构关系如下:

(root节点)  ->  Edge[label="abc"] -> (nodeA)
                       \
                        ...

2. 基础函数

创建节点
TrieNode *createNode() {
    TrieNode *node = (TrieNode *)malloc(sizeof(TrieNode));
    node->isEndOfWord = false;
    node->numChildren = 0;
    node->children = NULL;
    return node;
}
字符串工具函数

截取子串和计算公共前缀长度:

char *substr(const char *src, int start, int end) {
    int length = end - start;
    char *dest = (char *)malloc(sizeof(char) * (length + 1));
    strncpy(dest, src + start, length);
    dest[length] = '\0';
    return dest;
}

int commonPrefixLength(const char *s1, const char *s2) {
    int i = 0;
    while (s1[i] && s2[i] && s1[i] == s2[i]) {
        i++;
    }
    return i;
}

3. 插入字符串

插入函数的逻辑:

  1. 遍历当前节点的子边,查找公共前缀。
  2. 如果需要,拆分边并插入新字符串。
  3. 如果没有匹配,新建一条边。

完整代码:

void insertString(TrieNode *root, const char *key) {
    if (*key == '\0') {
        root->isEndOfWord = true;
        return;
    }

    for (int i = 0; i < root->numChildren; i++) {
        Edge *edge = &root->children[i];
        int prefixLen = commonPrefixLength(key, edge->label);

        if (prefixLen == 0) {
            continue;
        }

        if (prefixLen == (int)strlen(edge->label)) {
            insertString(edge->child, key + prefixLen);
            return;
        } else {
            char *commonPart = substr(edge->label, 0, prefixLen);
            char *oldLabelRem = substr(edge->label, prefixLen, strlen(edge->label));

            TrieNode *newNode = createNode();

            newNode->children = (Edge *)malloc(sizeof(Edge));
            newNode->children[0].label = oldLabelRem;
            newNode->children[0].child = edge->child;
            newNode->numChildren = 1;

            free(edge->label);
            edge->label = commonPart;
            edge->child = newNode;

            const char *keyRem = key + prefixLen;
            if (*keyRem == '\0') {
                newNode->isEndOfWord = true;
            } else {
                TrieNode *anotherChild = createNode();
                newNode->children = (Edge *)realloc(newNode->children,
                    sizeof(Edge) * (newNode->numChildren + 1));
                Edge *e = &newNode->children[newNode->numChildren];
                e->label = strdup(keyRem);
                e->child = anotherChild;
                newNode->numChildren++;
                anotherChild->isEndOfWord = true;
            }
            return;
        }
    }

    root->children = (Edge *)realloc(root->children,
            sizeof(Edge) * (root->numChildren + 1));
    Edge *newEdge = &root->children[root->numChildren];
    newEdge->label = strdup(key);
    newEdge->child = createNode();
    root->numChildren++;
    newEdge->child->isEndOfWord = true;
}

4. 搜索字符串

搜索函数逻辑与插入类似,逐步匹配字符串,直至找到或失败:

bool searchString(TrieNode *root, const char *key) {
    if (*key == '\0') {
        return root->isEndOfWord;
    }

    for (int i = 0; i < root->numChildren; i++) {
        Edge *edge = &root->children[i];
        int prefixLen = commonPrefixLength(key, edge->label);

        if (prefixLen == (int)strlen(edge->label)) {
            return searchString(edge->child, key + prefixLen);
        }
    }

    return false;
}

5. 测试示例

完整测试代码:

int main() {
    TrieNode *root = createNode();

    const char *words[] = {"he", "hello", "hi", "cat", "cater", "cart", "dog"};
    int n = sizeof(words) / sizeof(words[0]);

    for (int i = 0; i < n; i++) {
        insertString(root, words[i]);
        printf("Inserted: %s\n", words[i]);
    }

    const char *tests[] = {"he", "hello", "hi", "cat", "cater", "cart", "do", "dog", "c", "hea", "car"};
    int t = sizeof(tests) / sizeof(tests[0]);

    printf("\nSearch tests:\n");
    for (int i = 0; i < t; i++) {
        bool found = searchString(root, tests[i]);
        printf("'%s' -> %s\n", tests[i], found ? "FOUND" : "NOT FOUND");
    }

    return 0;
}

三、总结

  1. 压缩 Trie 能显著节省存储空间,尤其适合处理前缀相似的字符串集合。
  2. 插入和搜索操作需要特别注意边的拆分和动态内存管理。
  3. 可扩展功能包括删除操作、性能优化(如平衡化)、支持更大字符集(如 Unicode)等。
  4. 实际应用中,可根据需求优化实现(如使用哈希表代替数组存储边)。

http://www.kler.cn/a/517411.html

相关文章:

  • 【2024年华为OD机试】(A卷,200分)- 创建二叉树 (JavaScriptJava PythonC/C++)
  • 导出地图为图像文件
  • 二叉树的深度
  • 使用Vue3实现可拖拽的九点导航面板
  • Python Pandas数据清洗与处理
  • 数据结构初阶之栈的介绍与栈的实现
  • 浏览器hid 和蓝牙bluetooth技术区别
  • WPF 打印功能实现
  • LPDDR4 precharge和selfresh 详解
  • .NET9增强OpenAPI规范,不再内置swagger
  • 经典卷积网络算法-VGG16
  • SpringAI基于Ollama调用通义千问
  • Web3 的核心理念:去中心化如何重塑互联网
  • 不只是mini-react第二节:实现最简fiber
  • 状态模式
  • OpenAI模块重构
  • 43 继承
  • 【统计的思想】假设检验(二)
  • 对神经网络基础的理解
  • MATLAB支持的概率分布
  • Hive 知识点八股文记录 ——(三)区别和原理
  • Unity自学之旅05
  • mysql-023.增删查改进阶-表的设计,查询进阶
  • (算法竞赛)DFS深搜4——迷宫第一条路问题解析与代码实现
  • 2025数学建模美赛|赛题评析|难度对比|选题建议
  • SpringBoot开发(二)Spring Boot项目构建、Bootstrap基础知识