Trie树结构与实现

来源:互联网 发布:java 保存文本文件 编辑:程序博客网 时间:2024/05/17 00:58

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
原创粉丝点击