字符串算法--$\mathcal{KMP,Trie}$树

\(\mathcal{KMP算法}\)

实际上,完全没必要从\(S\)的每一个字符开始,暴力穷举每一种情况,\(Knuth、Morris\)和\(Pratt\)对该算法进行了改进,称为KMP算法。

而\(KMP\)的精髓在于,对于每次失配之后,我都不会从头重新开始枚举,而是根据我已经得知的数据,从某个特定的位置开始匹配;而对于模式串的每一位,都有唯一的“特定变化位置”,这个在失配之后的特定变化位置可以帮助我们利用已有的数据不用从头匹配,从而节约时间。

特点:1. \(i\) 不回退 2. \(j\) 回退的位置有讲究 3.构建一个辅助数组( \(nxt\) 数组)来跳过不必要的字符比较,从而提高搜索速度。

\(\mathcal{实现流程}\)

为了清楚地表述目的, \(T\) 与 \(S\) 失配前的部分作为 \(T'\) 来表述,此时寻找下一个开始匹配的标志头。而找到下一个标志头的方式为:

找到 \(T'\) 的最长相同前缀与后缀

\(\color{red}{这样找所有的前缀和后缀比较,是不是也是暴力穷举??那该怎么办呢??}\)

\(\color{red}{ans:当然是要用到动态规划递推啦。}\)

\(\mathcal{构建 Nxt 数组}\)

\(nxt\) 数组用于表示当前字符匹配失败时,模式串应该回退到哪个位置。对于模式串 \(p\) ,我们遍历其每个字符,并用一个指针 \(j\) 表示已匹配的字符数。当模式串中的两个字符匹配时,我们更新指针 \(j\) 的值,否则,我们回退 \(j\) 到 \(nxt[j]\) 的位置。通过这种方式,我们可以为模式串构建一个 \(nxt\) 数组,其中 \(nxt[i]\) 表示当模式串中第 \(i\) 个字符匹配失败时,应该回退到的位置。

\(\mathcal{实际字符匹配过程}\)

我们使用两个指针 \(i\) 和 \(j\) 分别遍历原字符串 \(s\) 和模式串 \(p\) 。如果当前字符匹配,则同时移动 \(i\) 和 \(j\) 。如果字符不匹配,我们根据 \(nxt\) 数组回退 \(j\) 的位置,直到找到匹配的字符或回退到模式串的开头。当 \(j\) 等于模式串长度 \(m\) 时,表示找到了一个匹配,输出匹配位置,并将 \(j\) 重置为 \(0\) 。

\(\mathcal{模板代码实现}\)

#include <iostream>
using namespace std;
const int N = 1e+6 + 10;
int nxt[N], n, m;
char p[N], s[N];
int main()
{
    cin >> n >> s + 1 >> m >> p + 1;
    // build next arraylist
    for (int i = 2, j = 0; i <= m; i++)
    {
        while (j && p[i] != p[j + 1]) j = nxt[j];
        if (p[i] == p[j + 1]) j++;
        nxt[i] = j;
    }
    // marry the str
    for (int i  = 1, j = 0; i <= n; i++)
    {
        while (j && s[i] != p[j + 1]) j = nxt[j];
        if (s[i] == p[j + 1]) j++;
        if (j == m) {
            cout << i - m << " ";
            j = 0;
        }
    }
    cout << endl;
    return 0;
}

\(\mathcal{Trie\,树}\)

字典树是一种高效的字符串数据结构,它通过将字符串的公共前缀合并在一起,节省空间并提高查询速度。

\(\mathcal{实现流程}\)

\(\mathcal{初始化变量和数据结构}\)

定义一个字典树结构( \(tree\) 数组)和一个记录字符串出现次数的数组( \(vis\) 数组)。同时定义一个计数器 \(flag\) 用于记录字典树中节点的数量。二维数组 \(tree\) 表示字典树的结构,其中 \(tree[i][j]\) 表示第 \(i\) 个节点的第 \(j\) 个子节点。

\(\mathcal{子功能实现}\)

\(\mathcal{insert}\)

实现一个 \(insert\) 函数,用于向字典树中插入一个字符串。它遍历字符串中的每个字符,将字符转换为数组下标(通过减去' \(a\) '并加上 \(1\) )。如果当前字符对应的子节点不存在,则创建一个新的节点并更新节点计数器。最后,在字符串末尾的节点中,更新字符串出现的次数。

\(\mathcal{query}\)

实现一个 \(query\) 函数,用于查询字典树中字符串的出现次数。它遍历字符串中的每个字符,将字符转换为数组下标。如果当前字符对应的子节点不存在,说明字符串不存在,查询结束。否则,将指针移动到子节点。最后,返回字符串末尾节点对应的出现次数。

\(\mathcal{主程序逻辑}\)

读取操作数量 \(n\) ,然后循环处理每个操作。对于每个操作,读取操作类型( \(ope\) )和操作字符串( \(str\) )。如果操作类型为 "\(i\)" ,调用 \(insert\) 函数插入字符串;如果操作类型为其他(例如查询操作),调用 \(query\) 函数查询字符串,并输出查询结果。

\(\mathcal{模板代码实现}\)

#include <iostream>
using namespace std;
const int N = 1e+6 + 10;
int n, flag = 1;
string ope, str;
int tree[N][27], vis[N][27];
void insert(string str)
{
    int pos = 0;
    int tmp = 0;
    for (int i = 0; i < str.size(); i++)
    {
        tmp = str[i] - 'a' + 1;
        if (tree[pos][tmp] == 0) tree[pos][tmp] = flag ++;
        pos = tree[pos][tmp];
    }
    vis[pos][tmp] ++;
}
int query(string str)
{
    int pos = 0;
    int tmp = 0;
    for (int i  = 0; i < str.size(); i++)
    {
        tmp = str[i] - 'a' + 1;
        if (tree[pos][tmp] == 0) break;
        pos = tree[pos][tmp];
    }
    return vis[pos][tmp];    
}
int main()
{
    cin >> n;
    while (n--)
    {
        cin >> ope >> str;
        if (ope == "i") insert(str);
        else cout << query(str) << endl;
    }
    return 0;
}
0 条评论
请不要发布违法违规有害信息,如发现请及时举报或反馈
还没有人评论呢,速度抢占沙发!
相关文章
  • 首先我们来了解什么是utc时间,协调世界时,又称世界统一时间、世界标准时间、国际协调时间。由于英文(CUT)和法文(TUC)的缩写不同,作为妥协,简称UTC。协调世界时是以原子时秒长为基础,在时刻上尽...

  • 志铭-2023年2月21日 0. 使用ParseName 注意:ParseName最多只能拆分为四列 ParseName只能针对.,所以若是其他的分隔字符,需要先替换为. WITH temp A...

  • 实验内容: 1、将文件上传到http://www.VirusTotal.com 进行分析并查看报告。文件匹配到了已有的反病毒软件特征吗?(国内用https://www.virscan.org/替代)2...

  • 我们结合运算符重载知识实现string 类 在自己实现的String类中可以参考C++中string的方法 例如构造,加法,大小比较,长度,[] 等操作. 当前的MyString 类中,暂时不加入迭代...

  • 引入eval-运行python表达式,导包语句:import datetime,然后调用datetime.datetime(2021,9,19,0,9,0),执行结果放在Object对象eval_re...

  • 前言     最近有人在某平台提问“C#的string是一种糟糕的设计吗?”,他认为官方为字符串做了很多内部优化处理,这种处理增加了理解成本,为什么不能提供char[]指针处理方案呢?   C#的st...

  • 一、利用strtok()函数进行分割函数头文件#include函数原型:char *strtok(char s[], const char *delim); s[]是原字符串,delim为分隔符函数返...

  • 在C语言中写字符串,我们一般这样定义变量 const char* str = "hello"; 乍看这样的赋值方式有点费解,前面是一个char*指针,str指向一个char字符的指针,而后面是一个字符...

  • 背景 最近发现某个数据采集的系统拿下来的数据,有些字段的JSON被莫名截断了,导致后续数据分析的时候解析JSON失败。 类似这样 {"title": "你好 或者这样,多了个双引号啥的 {"titl...

  • 在WPF中TextBlock的Text有时内容只需要改变个别数字,而不需要所以内容都修改,这时候就要使用StringFormat, 如: 这里面的xxx是个变量,那在Binding时应该怎样写呢 ...

  • 情形一 字符串的构造 一般会使用到的构造分为拷贝构造和直接构造 string str1="hello world";//拷贝构造 string str2(10,'c');//直接构造 string ...

  • 导读 ^ _ ^ KMP算法,大家一定非常熟悉啦!是非常高效的字符串匹配算法。模式串进行记忆实现跳转!下面我们将从最朴素的思路为起点,寻找可以优化的点。进而一步步实现KMP! 最朴素的暴力做法 在实现...

  • JZ58 左旋转字符串 题目 汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。 对于一个给定的字符序列 S ,请你把其循环左移 K 位后的序列...

  • 本文分享自华为云社区《GaussDB(DWS)字符串、二进制、十六进制互转》,作者:你是猴子请来的救兵吗 。 概述 现网中遇到很多小伙伴不清楚字符串与进制之间的转换方法,其实在GaussDB(DWS)...

  • package 常用类.String; import java.util.Arrays; import java.util.Locale; public class demo01 { pu...

  • 字符串匹配算法综述         对于字符串匹配算法,是在日常学习和工作中最常遇到的问题,字符串匹配算法要求输入主串(string)和子串(pattarn),然后返回子串在主串中第一次出现的位置。进...

  • 导读 ^ _ ^ Tire树是很关键的一种数据结构,你应该听过他的另外一个名字,即字典树,可以高效存储和查找字符串集合。 何为Tire树 一种高效存储和查找字符串集合的数据结构结尾进行标记,统计出现次...

  • String:字符串,使用一对双引号“”引起来表示。String声明为final的,不可以被继承String实现了Serializable接口:表示字符串是支持序列化的。(序列化在IO流会提及)Str...

  • String类 1.1 String类概述 Java中字符串属于对象,String类用于创建和操作字符串。 最简单的字符串创建:直接通过String创建 String str="工地佬"; 利用St...

  • 转自: http://www.java265.com/JavaCourse/202206/3733.html String简介:    string是C++、java、VB等编程语言中的字符串,字符串...