Trie树结构与实现

Trie树结构与实现

概述

​ 在平时使用搜索引擎或者一些其他工具时,常会有一些智能提示,或许我们需要搜索的内容并不存在,但这些工具都会尽量选择前缀尽可能相似的结果给我们。这里使用到的前缀匹配,大多则是使用Trie树进行处理。

Trie树基本定义

​ Trie树,又被称为前缀树或字典树,与其用途有关,Trie树是一种有序树,用于保存关联数组,其中的Key通常是字符串。与二叉查找树不同,Key并不直接存储在Trie树种,而是由节点在树中位置决定。一个节点的所有子节点具有相同前缀,也就是这个节点对应的字符串,即Key,根节点对应空字符串。

这里写图片描述

​ 上图即为一颗Trie树,由关键字集合{“a”, “to”, “tea”, “ted”, “ten”, “i”, “in”, “inn”} 构造而成。先来说明Trie树的特性:

  • 根节点不包含任何字符,除根节点外,任一节点都包含一个字符;
  • 从根节点到任一子节点的路径上,连接之后可以得到一个字符串,为该节点对应的字符串;
  • 每个节点的子节点包含的字符必须互不相同。

​ 由上图不难发现,若两个关键字具有相同前缀,那到达完整关键字节点的路径必有一段是相同的,所以Trie树的一个应用可以用来求解多关键字的最长公共前缀。下面则是求解最长公共前缀具体的代码实现,相关需要注意的点在注释中可以找到。

实现

typedef struct trie_tree {
    int cnt;  // 当前节点所代表字符出现次数
    struct trie_tree *next[26];  // 记录子节点的指针数组 
    trie_tree() : cnt(0) {
        for (int i = 0; i < 26; ++i) {
            next[i] = 0;
        }
    }
} trie_t;

​ 指针数组大小为26,仅是用于简单表示关键字由26个小写字母组合的情况,这个可以根据问题去做修改。根据需要求解的问题的不同,也可以针对结构体做一些相应修改,如词频统计或许需要一个bool值来判别是否为一个关键字。

void trie_create(const std::string &str, trie_t *root)
{
    int i;
    int len = (int) str.length();
    trie_t *cur = root;
    for (i = 0; i < len; ++i) {
        if (cur->next[str[i] - 'a'] == 0) {
            cur->next[str[i] - 'a'] = new trie_t();
        }

        cur = cur->next[str[i] - 'a'];
        ++(cur->cnt);
    }
}

​ 对于一个字符串将其添加进Trie树,若遇到不存在表示某字符的节点,则应创建一个。

std::string trie_search_lcp(trie_t *root, size_t strs_cnt, size_t min_len)
{
    std::string res;
    trie_t *cur = root;
    int i;
    int index;
    int scnt;  // 表示子节点个数 
    while (cur != 0) {
        scnt = 0;
        for (i = 0; i < 26; ++i) {  // 寻找next路径,以构成最长公共前缀 
            if (cur->next[i] != 0) {
                ++scnt;
                index = i;
            }
        }

        if (strs_cnt == 0) {  // 字符串集合为空,最长公共前缀为空
            return res;
        }
        else if (strs_cnt == 1) {  // 字符串集合仅有1个元素,最长公共前缀即为该元素
            if (scnt == 1) {  // next仅有1个节点
                res.push_back('a' + index);
                cur = cur->next[index];
            }
            else {  // next无节点
                cur = 0;
            }
        }
        else {  // 字符串集合有多于1个元素
            if (scnt == 1) {  // next仅有1个节点
                // 节点字符出现次数与集合元素数量相等
                if (cur->next[index]->cnt == strs_cnt) {
                    // 最长公共前缀的最大长度一定小于等于字符串集合中最短的字符串
                    if (res.length() < min_len) {
                        res.push_back('a' + index);
                    }
                }

                cur = cur->next[index];
            }
            else {  // 搜索到next不止有1个节点
                cur = 0;
            }
        }
    }
    return res;
}

​ scnt用于记录当前节点的子节点数,求解最长公共前缀时,子节点数超过1个,肯定就不是公共前缀了,注意这点。一些细节看代码注释即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值