Trie树(前缀树、字典树、单词查找树)

1 Trie树介绍

  • trie树是一种树形结构,是一种哈希树的变种。
  • 应用:典型应用于统计、排序和保存大量的字符串(但不限于字符串),经常被搜索引擎系统用于文本词频统计。
  • 优点:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

2 Trie树结构体

typedef struct trie{
    bool isEnd;
    struct trie *next[26];
} Trie;

单词sea、sell、she的Trie树:

Trie 中一般都含有大量的空链接,因此在绘制一棵单词查找树时一般会忽略空链接,为方便理解可以画成:

 2.1 Trie树的三个性质

  • 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
  • 从根节点到某个节点,路径上经过的字符连接起来,为该节点对应的字符串。
  • 每个节点的所有子节点包含的字符都不相同。

 这样看来,对于一个长为l的单词,无论是插入还是查询都是O(l)的时间复杂度

2.2 对Trie树的操作

2.2.1 插入

  • 插入就是将单词中的每个字母都逐一插入Trie树,插入前看这个字母对应的节点是否存在,若不存在就新建一个节点,否则就共享那个节点。

 向图中插入单词set

1、获取第一个字符s,判断p->next['s'-'a']!=NULL,说明根节点存在子节点s,则共享节点s

2、p = p->next['s'-'a']

3、获取第二个字符e,判断p->next['e'-'a']!=NULL,说明节点s存在子节点e,则共享节点e

4、p=p->next['e'-'a']

5、获取第三个字符t,判断p->next['t'-'a']==NULL,则说明节点e不存在子节点t,则新建子节点t

2.2.2 查询

  • 查询与插入操作类似,就是在Trie树中找到这个单词的每个字母, 若找到就继续找下去,若没有找到就可以直接退出了,因为没有找到就说明没有这个单词。

  • 以查询单词sea为例:

1、查询第一个字母s,发现根节点存在子节点s,则继续查询e

2、查询第二个字母e,发现s节点存在子节点e,则继续查询a

3、查询第三个字母a,发现e节点存在子节点a,并且节点a所指向的数据域为true,表明这个单词已经结束,说明存在单词sea。

 3 Trie树使用示例

请你实现 Trie 类:

  • Trie() 初始化前缀树对象。
  • void insert(String word) 向前缀树中插入字符串 word 。
  • boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
  • boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。
     

示例:

输入
["Trie", "insert", "search", "search", "startsWith", "insert", "search"]
[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]]
输出
[null, null, true, false, true, null, true]

解释
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple");   // 返回 True
trie.search("app");     // 返回 False
trie.startsWith("app"); // 返回 True
trie.insert("app");
trie.search("app");     // 返回 True
 

提示:

  • 1 <= word.length, prefix.length <= 2000
  • word 和 prefix 仅由小写英文字母组成
  • insert、search 和 startsWith 调用次数 总计 不超过 3 * 104 次

题目来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/implement-trie-prefix-tree

代码实现:

(1)C语言:

typedef struct trie{
    bool isEnd;
    struct trie *next[26];
} Trie;

//新建trie节点
Trie* trieCreate() {
    Trie * trie = malloc(sizeof(Trie));
    //初始化数据
    memset(trie->next, 0, sizeof(trie->next));
    trie->isEnd = false;
    return trie;
}

//插入单词
void trieInsert(Trie* obj, char * word) {
    int i;
    for(i=0; i<strlen(word); i++){
        char ch = word[i];
        if(obj->next[ch-'a'] == NULL){
            obj->next[ch-'a'] = trieCreate();
        }
        obj = obj->next[ch-'a'];
    }
    obj->isEnd = true;
}

//查询单词
bool trieSearch(Trie* obj, char * word) {
    int i;
    for(i=0; i<strlen(word); i++){
        char c = word[i];
        if(obj->next[c-'a'] == NULL){
            return false;
        }
        obj = obj->next[c-'a'];
    }
    return obj->isEnd;
}

//匹配前缀
bool trieStartsWith(Trie* obj, char * prefix) {
    int i;
    for(i=0; i<strlen(prefix); i++){
        int ch = prefix[i]-'a';
        if(obj->next[ch] == NULL){
            return false;
        }
        obj = obj->next[ch];
    }
    return true;
}

//释放内存
void trieFree(Trie* obj) {
    for (int i = 0; i < 26; ++i) {
        if (obj->next[i] != NULL) {
            trieFree(obj->next[i]);
        }
    }
    free(obj);
}

(2) java语言

class Trie {
    private boolean isEnd;
    private Trie[] next;

    public Trie() {
        this.isEnd = false;
        this.next = new Trie[26];
    }
    
    public void insert(String word) {
        Trie trie = this;
        for(int i=0; i<word.length(); i++){
            int index = word.charAt(i)-'a';
            if(trie.next[index] == null){
                trie.next[index] = new Trie();
            }
            trie = trie.next[index];
        }
        trie.isEnd = true;
    }
    
    public boolean search(String word) {
        Trie trie = this;
        for(int i=0; i<word.length(); i++){
            int index = word.charAt(i)-'a';
            if(trie.next[index] == null){
                return false;
            }
            trie = trie.next[index];
        }
        return trie.isEnd;
    }
    
    public boolean startsWith(String prefix) {
        Trie trie = this;
        for(int i=0; i<prefix.length(); i++){
            int index = prefix.charAt(i)-'a';
            if(trie.next[index] == null){
                return false;
            }
            trie = trie.next[index];
        }
        return true;
    }
}

4 结构体的内存释放

转载自:

C语言中free()函数释放struct结构体中的规律 - whitesad - 博客园 (cnblogs.com)

  • 在使用struct来定义并声明一个变量时,将会自动划分出一个连续的储存空间(虽然根据某些对齐原则会出现内存间隙,但是大体上来说还是连续的)。这一块连续空间将会包括结构体中的其他变量所需要的内存,如上图。
  • free()函数的作用是对动态分配的内存进行释放,这也就意味着当使用free函数清空一个结构体时,只会清空这个大框里的内存,而不会对a,b,c,d指向的内存进行清理
  • 所以,要是用free()函数释放结构体内存时,要先释放结构体中的指针类型变量所指向的内存,再释放动态分配的结构体的内存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值