Trie | (Insert and Search) (字典树)

本文介绍了一种高效的数据结构——Trie树(字典树),它能够优化关键字的检索过程。文章详细阐述了Trie树的基本原理,包括节点结构、插入和搜索操作,并提供了具体的代码实现。通过使用Trie树,可以将关键字的搜索复杂度降低到最优级别。

原文:http://www.geeksforgeeks.org/trie-insert-and-search/

Trie树(字典树)是一个高效的信息检索数据结构。利用trie树,搜索复杂度可以达到最优(关键字或者单词长度)。如果用二叉查找树存储关键字的话,一个比较平衡的BST需要M*logN的平均时间,这里M是最大字符串长度,N是树中关键字的个数。如果使用trie树,我们可以在O(M)的时间内查找关键字。然而trie的缺点就是内存要求较大。

Trie树中每个节点包含了多个分支,每个分支节点代表关键字的一个字母。我们将每个关键字的最后一个字母标记为叶节点。Trie的节点包含一个value域,用来区分内部节点和叶节点(这个value域还有其他的用处)。表示英文字母的节点的简单数据结构如下:

struct trie_node
{
    int value; /* Used to mark leaf nodes */
    trie_node_t *children[ALPHABET_SIZE];
};

在trie树中插入单词比较简单,关键字的每个字母作为单独的节点插入trie树中。注意节点的children域是一个指针数组,每个指针指向关键字的下一个字母。关键字的字母是children数组的索引。如果输入的关键字是新的或者是已经存在的关键字的扩展,需要创建新的节点,而且要标记叶节点。如果输入的关键字是trie树中存在的关键字的前缀,我们只需简单的标记输入关键字的最后一个字母作为叶节点。关键字的长度决定了trie得高度。

Trie树中搜索关键字的操作与插入操作类似,只需从trie的根节点开始,简单地向下比较关键字的字母和trie树的节点。当关键字完全匹配或者trie树中缺少某个字母的时候,搜索过程结束。如果trie树中的某个路径与关键字完全匹配,而且关键字最后一个字母对应的节点的value域为非零,那么这个关键字在trie树中存在。当搜索过程因为trie树中缺少某个字母而结束时,搜索过程并没有检查到关键字的所有字母,因此该关键字在trie树中不存在。

下图是代码中构造的trie树的情况:

在上面的图中,每个字母都是 trie_node_t类型。例如,root是trie_node_t类型,它的子节点a,b,t非空,其他的子节点都是NULL。同样,“a“的下一层只有一个子节点”n”,其他的子节点都为NULL。叶节点由蓝色的字母表示。

插入和搜索的复杂度为O(key_length),trie树的空间复杂度为O(ALPHABET_SIZE * key_length * N),这里N是trie树中关键字的数量。此外,有一些更有效率的trie节点表示方式(例如,压缩t的trie树,ternary search tree等等)来减小trie树的内存消耗。

trie树的实现和测试代码如下:

#include 
#include 
#include 

#define ARRAY_SIZE(a) sizeof(a)/sizeof(a[0])

// Alphabet size (# of symbols)
#define ALPHABET_SIZE (26)
// Converts key current character into index
// use only 'a' through 'z' and lower case
#define CHAR_TO_INDEX(c) ((int)c - (int)'a')

// trie node
typedef struct trie_node trie_node_t;
struct trie_node
{
    int value;
    trie_node_t *children[ALPHABET_SIZE];
};

// trie ADT
typedef struct trie trie_t;
struct trie
{
    trie_node_t *root;
    int count;
};

// Returns new trie node (initialized to NULLs)
trie_node_t *getNode(void)
{
    trie_node_t *pNode = NULL;

    pNode = (trie_node_t *)malloc(sizeof(trie_node_t));

    if( pNode )
    {
        int i;

        pNode->value = 0;

        for(i = 0; i < ALPHABET_SIZE; i++)
        {
            pNode->children[i] = NULL;
        }
    }

    return pNode;
}

// Initializes trie (root is dummy node)
void initialize(trie_t *pTrie)
{
    pTrie->root = getNode();
    pTrie->count = 0;
}

// If not present, inserts key into trie
// If the key is prefix of trie node, just marks leaf node
void insert(trie_t *pTrie, char key[])
{
    int level;
    int length = strlen(key);
    int index;
    trie_node_t *pCrawl;

    pTrie->count++;
    pCrawl = pTrie->root;

    for( level = 0; level < length; level++ )
    {
        index = CHAR_TO_INDEX(key[level]);
        if( !pCrawl->children[index] )
        {
            pCrawl->children[index] = getNode();
        }

        pCrawl = pCrawl->children[index];
    }

    // mark last node as leaf
    pCrawl->value = pTrie->count;
}

// Returns non zero, if key presents in trie
int search(trie_t *pTrie, char key[])
{
    int level;
    int length = strlen(key);
    int index;
    trie_node_t *pCrawl;

    pCrawl = pTrie->root;

    for( level = 0; level < length; level++ )
    {
        index = CHAR_TO_INDEX(key[level]);

        if( !pCrawl->children[index] )
        {
            return 0;
        }

        pCrawl = pCrawl->children[index];
    }

    return (0 != pCrawl && pCrawl->value);
}

// Driver
int main()
{
    // Input keys (use only 'a' through 'z' and lower case)
    char keys[][8] = {"the", "a", "there", "answer", "any", "by", "bye", "their"};
    trie_t trie;

    char output[][32] = {"Not present in trie", "Present in trie"};

    initialize(&trie);

    // Construct trie
    for(int i = 0; i < ARRAY_SIZE(keys); i++)
    {
        insert(&trie, keys[i]);
    }

    // Search for different keys
    printf("%s --- %s\n", "the", output[search(&trie, "the")] );
    printf("%s --- %s\n", "these", output[search(&trie, "these")] );
    printf("%s --- %s\n", "their", output[search(&trie, "their")] );
    printf("%s --- %s\n", "thaw", output[search(&trie, "thaw")] );

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值