【C语言】算法学习·前缀树实现

目录

Trie

初始化树节点 

插入字符串

查找前缀

销毁 

具体实现 

练习


Trie

Trie(发音类似 "try")或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。Trie又称前缀树或字典树,是一棵有根树,其每个节点包含以下字段:

        指向子节点的指针数组 next。
        布尔字段 isEnd,表示该节点是否为字符串的结尾

typedef struct Trie{
    bool isEnd; //该结点是否是一个串的结束
    struct Trie * next[26]; //字母映射表
}Trie;

初始化树节点 

对于每一个待插入的树节点来说,需要保存isEnd为字符串为结束,并且对于next数组指向NULL

/* false:表示未结束  true:表示结束*/
Trie* trieCreate() {
    Trie * obj = malloc(sizeof(Trie) * 1);
    for(int i = 0; i < 26; i++)
    {
        obj->next[i] = NULL;
    }
    obj->isEnd = false;
    return obj;
}

插入字符串

我们从字典树的根开始,插入字符串。对于当前字符对应的子节点,有两种情况:

子节点存在。沿着指针移动到子节点,继续处理下一个字符。
子节点不存在。创建一个新的子节点,记录在 children 数组的对应位置上,然后沿着指针移动到子节点,继续搜索下一个字符。


重复以上步骤,直到处理字符串的最后一个字符,然后将当前节点标记为字符串的结尾。

/*
*void trieInsert(Trie* obj, char * word)
void trieInsert:向前缀数插入字符串
Trie* obj:前缀树头结点
char * word:字符串
返回值:无
*/
void trieInsert(Trie* obj, char * word) {
    int len = strlen(word);
    for(int i = 0; i < len; i++)
    {
        if(obj->next[(word[i]-'a')] == NULL)
        {
            Trie * objNode = trieCreate();
            obj->next[(word[i]-'a')] = objNode;
        }
        obj = obj->next[(word[i]-'a')];
    }
    obj->isEnd = true;
}

查找前缀

我们从字典树的根开始,查找前缀。对于当前字符对应的子节点,有两种情况:

子节点存在。沿着指针移动到子节点,继续搜索下一个字符。
子节点不存在。说明字典树中不包含该前缀,返回空指针。
重复以上步骤,直到返回空指针或搜索完前缀的最后一个字符。

若搜索到了前缀的末尾,就说明字典树中存在该前缀。此外,若前缀末尾对应节点的 isEnd 为真,则说明字典树中存在该字符串。

/*
bool trieSearch(Trie* obj, char * word)
*bool trieSearch:检查字符串word是否在前缀树上
Trie* obj:前缀树头结点
char * word:字符串
返回值:存在返回true
        不存在返回false
*/
bool trieSearch(Trie* obj, char * word) {
    int len = strlen(word);
    for(int i = 0; i < len; i++)
    {
        if(obj->next[(word[i]-'a')] == NULL)
        {
            return false;
        }
        obj = obj->next[(word[i]-'a')];
    }
    if(obj->isEnd == false)
    {
        return false;
    }
    return true;
}

销毁 

对于已经存在的前缀树,递归销毁是比较方便的

void trieFree(Trie* obj) {
    for(int i = 0; i < 26; i++)
    {
        if(obj->next[i])
            trieFree(obj->next[i]);
    }
    free(obj);
}

具体实现 

typedef struct Trie{
    bool isEnd; //该结点是否是一个串的结束
    struct Trie * next[26]; //字母映射表
}Trie;

/* false:表示未结束  true:表示结束*/
Trie* trieCreate() {
    Trie * obj = malloc(sizeof(Trie) * 1);
    for(int i = 0; i < 26; i++)
    {
        obj->next[i] = NULL;
    }
    obj->isEnd = false;
    return obj;
}
/*
*void trieInsert(Trie* obj, char * word)
void trieInsert:向前缀数插入字符串
Trie* obj:前缀树头结点
char * word:字符串
返回值:无
*/
void trieInsert(Trie* obj, char * word) {
    int len = strlen(word);
    for(int i = 0; i < len; i++)
    {
        if(obj->next[(word[i]-'a')] == NULL)
        {
            Trie * objNode = trieCreate();
            obj->next[(word[i]-'a')] = objNode;
        }
        obj = obj->next[(word[i]-'a')];
    }
    obj->isEnd = true;
}
/*
bool trieSearch(Trie* obj, char * word)
*bool trieSearch:检查字符串word是否在前缀树上
Trie* obj:前缀树头结点
char * word:字符串
返回值:存在返回true
        不存在返回false
*/
bool trieSearch(Trie* obj, char * word) {
    int len = strlen(word);
    for(int i = 0; i < len; i++)
    {
        if(obj->next[(word[i]-'a')] == NULL)
        {
            return false;
        }
        obj = obj->next[(word[i]-'a')];
    }
    if(obj->isEnd == false)
    {
        return false;
    }
    return true;
}

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

void trieFree(Trie* obj) {
    for(int i = 0; i < 26; i++)
    {
        if(obj->next[i])
            trieFree(obj->next[i]);
    }
    free(obj);
}

/**
 * Your Trie struct will be instantiated and called as such:
 * Trie* obj = trieCreate();
 * trieInsert(obj, word);
 
 * bool param_2 = trieSearch(obj, word);
 
 * bool param_3 = trieStartsWith(obj, prefix);
 
 * trieFree(obj);
*/

练习

题1 力扣第208题

力扣https://leetcode.cn/problems/implement-trie-prefix-tree/

参考链接 

http://t.csdn.cn/2UBpShttp://t.csdn.cn/2UBpS思路

前缀:字符串的前缀是指字符串的任意首部。比如字符串“abbc”的前缀有“a”,“ab”,“abb”,“abbc”。
前缀树:前缀树是一种用于快速检索的多叉树结构,利用字符串的公共前缀来降低查询时间,核心思想是空间换时间,经常被搜索引擎用于文本词频统计。
每一个节点表示一个树的节点,然后每一个树的节点又可以指向26个子节点,这样子就可以构建一个字符串树

实现代码

typedef struct Trie{
    bool isEnd; //该结点是否是一个串的结束
    struct Trie * next[26]; //字母映射表
}Trie;

/* false:表示未结束  true:表示结束*/
Trie* trieCreate() {
    Trie * obj = malloc(sizeof(Trie) * 1);
    for(int i = 0; i < 26; i++)
    {
        obj->next[i] = NULL;
    }
    obj->isEnd = false;
    return obj;
}
/*
*void trieInsert(Trie* obj, char * word)
void trieInsert:向前缀数插入字符串
Trie* obj:前缀树头结点
char * word:字符串
返回值:无
*/
void trieInsert(Trie* obj, char * word) {
    int len = strlen(word);
    for(int i = 0; i < len; i++)
    {
        if(obj->next[(word[i]-'a')] == NULL)
        {
            Trie * objNode = trieCreate();
            obj->next[(word[i]-'a')] = objNode;
        }
        obj = obj->next[(word[i]-'a')];
    }
    obj->isEnd = true;
}
/*
bool trieSearch(Trie* obj, char * word)
*bool trieSearch:检查字符串word是否在前缀树上
Trie* obj:前缀树头结点
char * word:字符串
返回值:存在返回true
        不存在返回false
*/
bool trieSearch(Trie* obj, char * word) {
    int len = strlen(word);
    for(int i = 0; i < len; i++)
    {
        if(obj->next[(word[i]-'a')] == NULL)
        {
            return false;
        }
        obj = obj->next[(word[i]-'a')];
    }
    if(obj->isEnd == false)
    {
        return false;
    }
    return true;
}

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

void trieFree(Trie* obj) {
    for(int i = 0; i < 26; i++)
    {
        if(obj->next[i])
            trieFree(obj->next[i]);
    }
    free(obj);
}

/**
 * Your Trie struct will be instantiated and called as such:
 * Trie* obj = trieCreate();
 * trieInsert(obj, word);
 
 * bool param_2 = trieSearch(obj, word);
 
 * bool param_3 = trieStartsWith(obj, prefix);
 
 * trieFree(obj);
*/

题2         力扣第648题
力扣https://leetcode.cn/problems/replace-words/

 参考链接

http://t.csdn.cn/aKq0zhttp://t.csdn.cn/aKq0z思路

前缀:字符串的前缀是指字符串的任意首部。比如字符串“abbc”的前缀有“a”,“ab”,“abb”,“abbc”。
前缀树:前缀树是一种用于快速检索的多叉树结构,利用字符串的公共前缀来降低查询时间,核心思想是空间换时间,经常被搜索引擎用于文本词频统计。前缀树多用于字符串。
思路
将本题dictionary转换为前缀树,然后对sentence遍历,判断字符串前缀是否位于树上,存在就进行简化输出,不存在就完全输出,最后定义的字符串链接所有结果的比较结果
以下代码还可以优化

实现代码

//0:结束,1:没有结束
typedef struct word{
    int end;
    struct word * next[26];
}word;

//初始化前缀树
word * trieCreate(){
    word * obj = malloc(sizeof(word) * 1);
    for(int i = 0; i < 26; i++)
    {
        obj->next[i] = NULL;
    }
    obj->end = 0;;
    return obj;
}
/*
*void word_create(word * root, char ** dictionary, int inode)
void word_create:向前缀数插入字符串
word * root:前缀树头结点
char ** dictionary:字符串
int inod:字符串行下标
返回值:无
*/
void word_create(word * root, char ** dictionary, int inode)
{
    int len = strlen(dictionary[inode]);
    for(int i = 0; i < len; i++)
    {
        if(root->next[dictionary[inode][i] - 'a'] == NULL)
        {
            word * n = trieCreate();
            root->next[dictionary[inode][i] - 'a'] = n;
        }
        root = root->next[dictionary[inode][i] - 'a'];
    }
    root->end = 1;
}
//判断字符串是否在前缀树上
char * str_if(word * root, char * sentence, int len_left, int len_right)
{
    int len = 0;
    for(int i = len_left; i < len_right; i++)
    {
        if(root->next[sentence[i] - 'a'] && root->end == 0)
        {
            len++;
            root = root->next[sentence[i] - 'a'];
        }
        else
        {
            if(root->end == 0)
            {
                len = len_right - len_left;
            }
            break;
        }
    }
    if(len)
    {
        char * str = malloc(sizeof(char) * (len + 1 + 1));
        strncpy(str, sentence+len_left, len);
        str[len] = ' ';
        str[len + 1] = '\0';
        return str;
    }
    else
    {
        char * str = malloc(sizeof(char) * (len_right - len_left + 1 + 1));
        strncpy(str, sentence+len_left, len_right - len_left);
        str[len_right - len_left] = ' ';
        str[len_right - len_left + 1] = '\0';
        return str;
    }
    return NULL;
}
//优化字符串sentence
char * replaceWords(char ** dictionary, int dictionarySize, char * sentence){
    char * str = malloc(sizeof(char) * (1000 * 1000 + 1));
    str[0] = '\0';
    word * root = trieCreate();
    for(int i = 0; i < dictionarySize; i++)
    {   
        word_create(root, dictionary, i);
    }
    int len = strlen(sentence);
    int len_left = 0;
    int i;
    for(i = 0; i < len; i++)
    {
        if(sentence[i] == ' ')
        {
            strcat(str , str_if(root, sentence, len_left, i));

            len_left = i+1;
        }
    }
    strcat(str , str_if(root, sentence, len_left, i));
    len = strlen(str);
    str[len - 1] = '\0';
    trieFree(root);
    return str;
}
//销毁前缀树
void trieFree(word * obj) {
    for(int i = 0; i < 26; i++)
    {
        if(obj->next[i])
            trieFree(obj->next[i]);
    }
    free(obj);
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值