字典树tire 多模式串

引言

考虑这样的问题:
给出模式串S1,S2,S3…Sn 和文本串txt , 问txt是多少个模式串的前缀
egs:

S1: fusufusu
S2: fusualex
S3: anmikusu
txt: fusu

ans = 2 (txt是S1,S2的一个前缀)

如果我们是暴力匹配每一个模式串和文本串,那么时间复杂度为O(n * m * k)(稍微表示一下?)
那么有无快一点的法子呢?Tire 字典树

字典树

字典树是什么

这里说的树不是那个令人头疼用指针搞出来的树,只是二维数组被抽象成树,请放心观看 (

在算法中通常是以英文字母作为字符串的元素, 如果考虑单个模式串,那么将它化成一棵树的样子会是什么亚子呢?(以fusufusu)为例子
在这里插入图片描述
是的,长得有点抽象了,但是我们确实叫他树:)。接下来我们加入其他模式串
在这里插入图片描述
貌似还是很抽象,不管了!

我们会对每一个节点进行编号,每个节点会有26个字母个子节点,那么就会构成一个二维数组 tire[maxn][26]; 这就是字典树了

字典树构建

目前只有一个根节点:
在这里插入图片描述
现在假设要插入eng , 且有一个p指针指向root节点(这里的指针不是c语言中的指针,只是一个箭头而已)
当前指针p没有指向e的子节点,那么创建一个新的节点,值赋值为e,修改p指向新节点
在这里插入图片描述
接下来的ng同样的过程建立
在这里插入图片描述
当前遍历到了eng模式串的结尾,那么ed[p]++(前文说了ed数组统计模式串结尾个数)

现在插入一个新的模式串enh
同样p指向root,发现当前指向节点具有子节点,那么p直接指向e,同样的n也是如此
但是之后p不具备h这个新节点,那么新建节点同时赋值为h,p移动到新节点
在这里插入图片描述
同时ed[p]++;

代码实现:
首先声明需要的变量:

  • tire[maxn][26] 字典树储存
  • cnt 记录除根节点外,节点总数
  • ed[maxn] 记录以i结尾的模式串的个数ed[i]
void insert(char str[]) {  //插入模式串(构造字典树)
    int p = 0;	//p为当前所在节点位置
    int len = strlen(str);
    for(int i=0;i<len;i++) {
        if( !tire[p][str[i]-'a'] ) {		//当前节点的str[i]子节点不存在
            tire[p][str[i]-'a'] = ++cnt;	//创建新节点
            clear(cnt);		//初始化该节点
        }
        p = tire[p][str[i]-'a'];	//当前节点移动到下一位
    }
    ed[p]++;
}

void clear(int pos){	//节点初始化
    for(int i=0;i<26;i++){
        tire[pos][i] = 0;
    }
}

str[i] - ‘a’ 条件是模式串只包含小写字母,而字母的编码是按顺序且‘a’编号最小

字典树query

如果只是查询文本串是否在众多模式串中出现过,那么很简单,只需要拿着文本串在字典树中匹配一遍,看看能不能匹配到文本串结尾

bool query(char txt[]){     //查询文本串是否在模式串中
    int p = 0;      //p指针
    int len = strlen(txt);
    
    for(int i = 0;i < len;i++){
        if( tire[p][ txt[i]-'a' ] ){    //p有txt[i]这个子节点
            p = tire[p][ txt[i]-'a' ]
        } else{     //没有, 直接返回false
            return false;
        }
    }
    return true;
}

引言中的问题是,文本串是多少个模式串中前缀,那么 需要加入一个新的数组countNum[maxn] ,作用和ed数组类似,count数组记录的是有多少个模式串经过了当前节点

那么inster函数稍微修改一下:

void insert(char str[]) {  //插入模式串(构造字典树)
    int p = 0;	//pos为当前所在节点位置
    int len = strlen(str);
    for(int i=0;i<len;i++) {
        if(!tire[p][str[i]-'a']) {		//当前节点的str[i]子节点不存在
            tire[p][str[i]-'a'] = ++cnt;	//创建新节点
            clear(cnt);		//初始化该节点
        }
        p = tire[p][str[i]-'a'];	//当前节点移动到下一位
        countNum[p]++;		//记录多少个模式串经过
    }
    ed[p]++;
}

上述query函数也需修改:

int query(char txt[]){     //查询文本串是否在模式串中
    int p = 0;      //p指针
    int len = strlen(txt);

    for(int i = 0;i < len;i++){
        if( tire[p][ txt[i]-'a' ] ){    //p有txt[i]这个子节点
            p = tire[p][ txt[i]-'a' ];
        } else{     //没有, 直接返回0
            return 0;
        }
    }
    return countNum[p];
}

end

练习题地址:洛谷-字典树模板注意这题含大小写字母和数字

字典树是很基础的数据结构了,基本不需要长篇大论就可以讲清楚,如果文章有错误或者不足之处请指出
另外,字典树+KMP(思想)可以引出一个新的算法:AC自动机
用于解决多模式串匹配问题

自我引流一下:
浅谈KMP
AC自动机+拓扑优化

感谢看到最后 😃😃

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值