首页 > 编程语言 >串的匹配算法:Brute-Force 与 KMP

串的匹配算法:Brute-Force 与 KMP

时间:2023-08-25 21:44:29浏览次数:37  
标签:Force Brute next start 算法 KMP 匹配

目录

串的匹配算法:Brute-Force 与 KMP

串的匹配算法是求子串在主串位置的算法。本次介绍的算法是在指定了从主串特定位置后寻找第一个匹配字串的位置。

在介绍算法前,先定义几个变量:主串 S、字串 T、要求从主串匹配的起始位置 pos、某次匹配时主串的开始位置 start(start 初始化为 pos)。

这里介绍两种算法:Brute-Force(布鲁特-福斯)算法与 KMP 算法。

Brute-Force(布鲁特-福斯)算法

Brute-Force 算法是一种带回溯的匹配算法,其基本思想是:将 S 与 T 进行匹配过程中,成功返回对应的 start,否则 start 加一,重新开始匹配,直至匹配成功或者 start 到了 S 的末尾。

以下是相关实现的 C++ 代码:

/**
 * @brief Brute-Force 算法实现。	
 * @param s 主串
 * @param pos 主串起始位置
 * @param t 子串
 * @note 需要包含头文件 string
 * @return 成功返回位置序号,否则返回 -1。
 */
int Brute_Force(const std::string s, const int pos, const std::string t)
{
    if (t.length() == 0) return 0; // 字串为空匹配任意串
    int i, j, start;
    i = start = pos; j = 0;
    while (i < s.length() && j < t.length())
    {
        if (s.at(i) == t.at(j)) { ++i; ++j; }
        else { i = ++start; j = 0; }
    }
    if (j >= t.length()) return(start);
    else return(-1);
}

该算法思路比较简单,但时间复杂度高,最坏情况下为$O(s.len \times t.len)$。

KMP 算法

Brute-Force 算法末次匹配失败时,start 加一。KMP 算法改进了这种方式,其改进的思想是:在匹配失败时,“会从中吸取信息”,得到主串中 start 位置后面某些位肯定无法匹配上的位置,跳过这些位置,start 加到下次可能会匹配的上的位置。

如何获得下次 start 的位置?首先先看以下某次匹配的示例:

对于该示例,S[5] 与 T[5] 未匹配时,Brute-Force 算法的 start 会向后移动一步。但仔细观察 T,在已经匹配成功的 T[0]-T[4] 中,有相同的内容 “ab”,它也匹配了主串上的内容,其实下次匹配时,start 可以移动更多,将字串的前缀“ab”移动到后缀“ab”匹配到的主串位置。

可能你会有疑问,如果中间也有相同的前后缀怎么办?比如以下例子:

这时就错误了。KMP 算法考虑到这一点,所以它要求的是“最大的”前后缀(本身与本身相等,所以以本身为最大前后缀没意义)。上述例子的最大前后缀是“aba”,实际匹配应该是这样:

这时中间依然有相同的前后缀“aba”,但把前缀“aba”对应到中间的“aba”是肯定匹配不上的,因为如果把前缀“aba”对应到中间的“aba”时成功匹配了字串,那么对于上述例子来说,T[4]-T[11] 和 T[0]-T[7] 的内容相同,“aba” 就不是最大前后缀,与假设相悖。

理解了 KMP 算法改进的原理,我们就只需要建立一个保存 T 最大前后缀的数组,回溯失败时 start 移动步数根据该数组添加相应值就行,我们暂且把这个数组取名为 next。KMP 算法难点不在其思想,而是它快速求 next 数组的方式。

它求 next 数组的思想其实是利用了数学归纳法。已知 next[0]、next[1]、……、next[i-1],根据它们求 next[i]。

比如有以下字串 T,并且也知道了一些对应的 next 数组值:

求 next[i - 1] 时,发现 T[4] 与 T[i - 1] 相等,也就是 T[next[i - 2]] 与 T[i - 1] 相等,那么 next[i - 1] 其实就等于 next[i - 1] + 1。那么如果不等呢?如求 next[i] 时:

要想能让 T[0]-T[i] 的前缀与后缀匹配,对于上述例子,就是要让 T[0]-T[4] 的某个前缀与 T[i - 5]-T[i-1] 的某个后缀匹配的同时,让该前缀的后一个字符与 T[i] 匹配。前者条件很容易实现,因为 T[0]-T[4] 与 T[i - 5]-T[i-1] 相同,前者条件就是找 T[0]-T[4]/T[i - 5]-T[i-1] 的最大前后缀,该值就是 next[next[i -1] - 1],即 next[2] 为 2。然后实现后一个条件,比较 T[2] 与 T[i],这里相等,就为 next[next[i -1] - 1] + 1,即 next[2] + 1 为 3。如果不相等,则继续匹配“最大前后缀的最大前后缀”,即 “ab” 的最大前后缀,继续执行上述步骤,直到找到相等的位置而至。

以下是一个 C++ 代码实现:

/**
 * @brief next 数组初始化,不含 next[0],它已被初始化为 0。
 * @param t 上次与 next 的第 i 位比较的位置,每次计算 next[i] 时,t 会被初始化为 i。
 * @param i next 数组的第 i 位。
 * @param next next 数组。
 * @param T 字串。
 */
void next_init(int t, int i, std::vector<int> & next, const std::string & T)
{
    t = next[t - 1];
    if (T[t] == T[i]) next[i] = t + 1;
    else
    {
        if (t == 0) next[i] = 0;
        else next_init(t, i, next, T);
    }
}

/**
 * @brief KMP 算法实现。
 * @param s 主串
 * @param pos 主串起始位置
 * @param t 子串
 * @note 需要包含头文件 string
 * @return 成功返回位置序号,否则返回 -1。
 */
int KMP(const std::string s, const int pos, const std::string t)
{
    if (t.length() == 0) return 0;
    int i, j, start;
    i = start = pos; j = 0;
    std::vector<int> next(t.length(), 0);
    for (int i = 1; i < t.length(); i++)
    {
        int j = i;
        next_init(j, i, next, t);
    }
    while (i < s.length() && j < t.length())
    {
        if (s.at(i) == t.at(j)) { ++i; ++j; }
        else { 
            if (j == 0) { i = ++start; j = 0; }
            else
            {
                start += j - next[j - 1];
                i = start; 
                j = 0; 
            }
        }
    }
    if (j >= t.length()) return(start);
    else return(-1);
}

KMP 算法相较于 Brute-Force 算法,它以空间复杂度换取了时间复杂度。它的时间复杂度为$O(s.len + t.len)$

参考文献

标签:Force,Brute,next,start,算法,KMP,匹配
From: https://www.cnblogs.com/meyok/p/17658015.html

相关文章

  • Codeforces Round 894 (Div. 3) A-F题解
    A.GiftCarpet题意最近,特马和维卡庆祝了家庭日。他们的朋友Arina送给他们一块地毯,这块地毯可以用拉丁文小写字母的\(n\cdotm\)表来表示。维卡还没看过礼物,但特马知道她喜欢什么样的地毯。如果维卡能在地毯上读出自己的名字,她一定会喜欢的。她从左到右逐列阅读,并从当前列中......
  • salesforce是一家怎么样的公司
    某咨询公司A,经常为客户执行软件实施项目,他们集成了各种各样的厂商,比如阿里云、亚马逊云等,salesforce也是他们集成的一家厂商之一。本文呢,介绍下这家公司的产品、利润来源等。产品主要是线上软件,提供的功能包括产品、订单、机会、销售、联系人管理等。其软件可根据用户要求定制化。......
  • Winter '24发布在即,Salesforce Flow中的最热功能不容错过!
    FlowBuilder作为自动化领域的新秀,它在功能方面已经远远超过WorkflowRules和ProcessBuilder,随着WorkflowRules和ProcessBuilder的退役,目前所有自动化都需要迁移到Flow。Winter'24发布在即,Flow中的亮点功能不容错过!一起来先睹为快吧~01在Record-TriggeredFlow中创建自定......
  • CodeForces1741G-Kirill and Company题解
    \(\large\text{CodeForces1741G-KirillandCompany题解}\)题面传送门(有翻译(由黄巨佬提供))思路预处理因为\(k\)很小,所以可以先bfs预处理出家在\(i(2\lei\len)\)的人能捎带上哪些集合的人,在表示集合时用一个\(0\)到\(2^k-1\)的整数\(j\)表示,若\(j\)在二进质下的......
  • salesforce email log
    由于网络是不可靠的,所以是否发送除了逻辑还有可能是邮箱问题referencehttps://fairsail.my.site.com/sagepeoplecustomercommunity/s/article/How-To-Run-And-Check-Email-Logs#:~:text=R-ReceptionTheemailwassuccessfullyreceived.,Salesforcewillretrydeliveryove......
  • MySQL 索引提示 - FORCE INDEX
    概述 在MySQL中,FORCEINDEX是一种查询提示,用于强制查询优化器使用特定索引来执行查询。查询优化器在执行查询时,会根据统计信息和查询条件等来选择最优的执行计划,包括选择哪个索引来提高查询性能。但有时候查询优化器可能会选择非最优的索引,或者无法识别最适合的索引,这时可以使......
  • Codeforces Round #849 (Div. 4) 题解
    第一次打$\text{Div.4}$,感觉体验还行,差一题AK。##A直接使用if语句判断某个字符是否在字符串$\text{codeforces}$中出现过,幼儿园小朋友都会做。时间复杂度$\mathcal{O}(T)$,空间复杂度$\text{O}(1)$。ACCode##B用两个变量$x,y$记录当前走到哪里。若出现过$x=1,y=1$的......
  • 全球Salesforce顾问薪资大揭秘!顾问如何升职加薪?
    Salesforce顾问通过针对业务挑战提出、记录和分析需求,提供解决方案,从而帮助企业改善Salesforce的流程和效率。顾问是企业和Salesforce之间的桥梁。Salesforce顾问的薪资一直是生态系统中的热门话题,备受求职者关注。本篇文章将分享提高顾问薪资的最直接、最有效的方式!初级顾问......
  • Educational Codeforces Round 109 (Rated for Div. 2)
    B题没想到被坑了两次,极端情况明明也很好想,硬是WA了两发。C题很想之前做过的经典蚂蚁题,但是又不太一样,但分析之后,发现之后奇偶性相同才可能碰撞,那么分开处理,假如已经有相向而行,肯定是最快碰撞的,用一个栈维护即可,最后就是剩下的肯定是LLL...RRR将它们配对即可。#inclu......
  • 「题解」Codeforces 825G Tree Queries
    点权转边权,把边权设为两个端点的\(\min\),然后发现询问\(x\)的答案,就是询问\(x\)与所有黑点的虚树,边权的\(\min\)是多少。假设要判定答案是否\(\geqk\),那么就是询问\(x\)只经过\(\geqk\)是否能到达所有黑点,于是想到建立Kruskal重构树,那么\(x\)与所有黑点的LCA......