字典树(Trie)初步理解

  写在开头:字典树,再社会也是棵树!

  字典树,又名前缀树,洋名Trie,是一种树型数据结构。其特点就是专门用于存储字母单词(其实数字也行啦),占有空间大,但效率比哈希树稳定,孰优孰劣,视场景而定。

  先考虑这样几个字符串{ “i”, “pen”, “apple”, “pineapple”, “app”, “apap”, “pine”, “pine” },并将他们以字典树结构存储,如下图1所示:

《字典树(Trie)初步理解》 标题

  观察上图,有一个根源结点(不存储数据),然后以字典的顺序存放第一行数据(每个字符串开头的字符),按照字符串长度依次存储,如果字符串所有的字符都存入了,就给最后一个字符做标记。如果需要统计一个字符串出现了几次,再给最后一个字符的标记不断加1即可。

  我们存储的字符串有一些特点:有的具有相同前缀,如”app”和 “apap” 有 “ap”,但在后面有分叉;还有的不仅具有相同前缀,而且在同一条路上,如 “pine” 和 “pineapple”。所以具有相同前缀的,其前缀都会在一条路上,因此这种结构也称前缀树。

  最后,我们考虑查找问题。当我们存好所有数据后,再去查找就如同翻字典一样。首先找到要搜索字符串的首字母,然后,顺着它继续找下一个字母,直到搜索完最后一个。

  我参考了神奕博主的代码实现,下面是转载其第四部分的代码 (加入了新的注释)。

#include <iostream>
#include <string>
#include <stdio.h>
using namespace std;

#define ALPHABET_SIZE 26  //定义结点有26个大小(默认为26个小写字母,可随实际修改)

typedef struct trie_node
{
    int count;   // 标记,记录该节点代表的单词的个数
    trie_node *children[ALPHABET_SIZE]; // 各个子节点 
}*trie;

trie_node* create_trie_node()
{
    trie_node* pNode = new trie_node();  //创建一棵新树结点
    pNode->count = 0;
    for(int i=0; i<ALPHABET_SIZE; ++i)   //每个结点都有26个空间,默认为NULL
        pNode->children[i] = NULL;
    return pNode;
}

void trie_insert(trie root, char* key)    //字符串插入函数
{
    trie_node* node = root;    //建立新树,root为根源结点,将root赋值给node
    char* p = key;    //新建p指针,并获得key字符串的首地址
    while(*p)    //遍历key字符串
    {
        if(node->children[*p-'a'] == NULL)    //如果录入字符是新点,就创建新结点。
        {
            node->children[*p-'a'] = create_trie_node();    //创建
        }
        node = node->children[*p-'a'];    //将node指向当前遍历结点
        ++p;    //字符串指针地址加1,即获取下一个字符
    }
    node->count += 1;    //最后一个字符,也是最后一个结点的标记加1
}

/**
 * 查询:不存在返回0,存在返回出现的次数
 */ 
int trie_search(trie root, char* key)    //字符串完全匹配,基础查询
{
    trie_node* node = root;    //获得根源结点root,赋值给node
    char* p = key;    //获取字符串首地址
    while(*p && node!=NULL)    //如果字符串遍历到'\0'(最后),或者字符所在的结点不存在,就退出查询
    {
        node = node->children[*p-'a'];    //获取当前字符结点
        ++p;    //字符串地址加1,获取下一个字符
    }

    if(node == NULL)    //如果结点为空,意味着字符串有字符不存在于树中,即不存在此字符串。
        return 0;
    else
        return node->count;    //如果存在,就返回存在的次数,至少为1
}

int main()
{
    // 关键字集合
    char keys[][8] = {"the", "a", "there", "answer", "any", "by", "bye", "their"};
    
    trie root = create_trie_node(); //新建根源结点
    
    for(int i = 0; i < 8; i++)    // 创建trie树
        trie_insert(root, keys[i]);

    // 检索字符串,测试
    char s[][32] = {"Present in trie", "Not present in trie"};
    printf("%s --- %s\n", "the", trie_search(root, "the")>0?s[0]:s[1]);
    printf("%s --- %s\n", "these", trie_search(root, "these")>0?s[0]:s[1]);
    printf("%s --- %s\n", "their", trie_search(root, "their")>0?s[0]:s[1]);
    printf("%s --- %s\n", "thaw", trie_search(root, "thaw")>0?s[0]:s[1]);

    return 0;
}

  最后提醒注意,如果存入的只有小写字符,那么根据ascll码存放就是【字符-‘a’】; 如果是数字则是 【字符-‘0’】,大写字母类似,若为混合,也就是将范围再扩大,按实际需要编写。

    原文作者:Trie树
    原文地址: https://blog.csdn.net/li13168690086/article/details/81366718
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞