时间复杂度O(n)的子串查找算法。

经典实例

主字符串(s):abcabcabd
模式串(t):abcabd

比较次数    主字符串    模式串    备注
一    abcabcabd    abcabd    红色和绿色表示正在比较的子串,红色表示不同部分,绿色表示相同部分。
二    abcabcabd    abcabd    
三    abcabcabd    abcabd    
四    abcabcabd    abcabd    
五    abcabcabd    abcabd    
六    abcabcabd    abcabd    ab是abcab的公共前后缀,abcab是上次(第五次比较成功的子串)
七    bcab    abca    
八      cab    abc    
九      ab    ab    

观点:只需要比较上次相等部分的公共前后缀

假定一:s[i1...i2)等于t[0...j)
假定二:s[i2]不等于t[j]
假定二的意思是i1不是find的返回值。
假定三:x取[i1...i2),字符串s[x...i2)的长度是len=i2-x。
假定一和假定三可以得出推理一:s[x,i2)等于t[j-len...j)。
结合推理一,如果t[0...j-len)不等于t[j-len,j)则t[0...j-len)不等于s[x...i2),也就是s[x...]不是find返回值。
结论一:如果t[0...j)长度为len的前缀和后缀不相等,则x不是结果,直接忽略。
结论二:如果t[0...j)长度为len的前缀和后缀相等,则t[x...j)和s[0...len)相等,直接比较t[j]和s[len)。
从最长公共前缀处理还是从最短公共前缀开始
i1递增的过程和从最长公共前缀到最短公共前缀的过程。

不需要记录所有公共前缀

只需要记录最长公共前缀,然后递归或迭代求。因为:次长公共前缀就是最长公共前缀的最长公共前缀。

说明

s[i,j)表示从i到j的子串,包括i不包括j。S[x...]表示从索引k开始的子串,长度未定。
字符串s[0,j)公共前后缀指的是s[0,x)等于s[j-x,j),x不等于j,也就是公共前后缀必能是本身。
获取最长公共前后缀
如果s[0,j)的公共前缀为x,如果x大于0,则必定有s[0,j-1)的前缀为x-1。所以只需要比较s[0,j-1)的公共前后缀。

核心代码

class KMP
{
public:
    virtual int Find(const string& s,const string& t )
    {
        CalLen(t);
        m_vSameLen.assign(s.length(), 0);
        for (int i1 = 0,  j = 0; i1 < s.length(); )
        {
            for (; (j < t.length()) && (i1 + j < s.length()) && (s[i1 + j] == t[j]); j++);
            //i2 = i1 + j 此时s[i1,i2)和t[0,j)相等 s[i2]和t[j]不存在或相等
            m_vSameLen[i1] = j;
            //t[0,j)的结尾索引是j-1,所以最长公共前缀为m_vLen[j-1],简写为y 则t[0,y)等于t[j-y,j)等于s[i2-y,i2)
            if (0 == j)
            {
                i1++;
                continue;
            }
            const int i2 = i1 + j;
            j = m_vLen[j - 1];
            i1 = i2 - j;//i2不变
        }
        
        for (int i = 0; i < m_vSameLen.size(); i++)
        {//多余代码是为了增加可测试性
            if (t.length() == m_vSameLen[i])
            {
                return i;
            }
        }
        return -1;
    }
protected:
    void CalLen(const string& str)
    {
        m_vLen.resize(str.length());
        for (int i = 1; i < str.length(); i++)
        {
            int next = m_vLen[i-1];
            while (str[next] != str[i])
            {
                if (0 == next)
                {
                    break;
                }
                next = m_vLen[0];
            }
            m_vLen[i] = next + (str[next] == str[i]);
        }
    }
    int m_c;
    vector<int> m_vLen;//m_vLen[i] 表示t[0,i]的最长公共前后缀
    vector<int> m_vSameLen;//m_vSame[i]记录 s[i...]和t[0...]最长公共前缀,增加可调试性
};

测试代码

class CTestKMP :public KMP
{
public:
    virtual int Find(const string& s, const string& t) override
    {
        int iRet = KMP::Find(s,t);
        for (int i = 0; i < m_vLen.size(); i++)
        {
            std::cout << t.substr(0, i + 1).c_str() << " " << m_vLen[i] << std::endl;
        }
        return iRet;
    }
    void Assert(const vector<int>& vLen,const vector<int>& vSameLen)
    {
        for (int i = 0; i < vLen.size(); i++)
        {
            assert(vLen[i] == m_vLen[i]);
        }
        for(int i = 0 ; i < vSameLen.size();i++)
        {
            assert(vSameLen[i] == m_vSameLen[i]);
        }
    }
};

int main()
{
    vector<string> ss = { "abcabcabd","abc","abcb","cabaab"};
    vector<string> ts = { "abcabd" ,"d","b","ab"};
    vector<vector<int>> lens = { {0, 0, 0, 1, 2, 0},{0},{0},{0,0} };
    vector<vector<int>> sameLens = { {5, 0, 0, 6, 0, 0,0,0,0},{0,0,0},{0,1,0,1},{0,2,0,1,2,0 } };
    for (int i = 0; i < ss.size(); i++)
    {
        CTestKMP kmp;
        auto res = kmp.Find(ss[i], ts[i]);
        kmp.Assert(lens[i], sameLens[i]);
    }
    
}

开发测试环境

Win10 VS2022

如果格式混乱影响阅读,请到CSDN下载word版。目前审核中,请稍候!

作者的话

KMP确实比较难理解,我学习了多次。并且重写了至少两次。希望这次是真懂了。

较难理解的字符串查找算法KMP的更多相关文章

  1. KMP 算法 & 字符串查找算法

    KMP算法 Knuth–Morris–Pratt algorithm 克努斯-莫里斯-普拉特 算法 algorithm kmp_search: input: an array of character ...

  2. Rabin-Karp字符串查找算法

    1.简介 暴力字符串匹配(brute force string matching)是子串匹配算法中最基本的一种,它确实有自己的优点,比如它并不需要对文本(text)或模式串(pattern)进行预处理 ...

  3. Rabin-Karp指纹字符串查找算法

    首先计算模式字符串的散列函数, 如果找到一个和模式字符串散列值相同的子字符串, 那么继续验证两者是否匹配. 这个过程等价于将模式保存在一个散列表中, 然后在文本中的所有子字符串查找. 但不需要为散列表 ...

  4. 字符串查找算法的改进-hash查找算法

    字符串查找即为特征查找: 特征即位hash: 1.将待查找的字符串hash: 2.在容器字符串中找头字符匹配的字符串,并进行hash: 3.比较hash的结果:相同即位匹配: hash算法的设计为其中 ...

  5. 字符串查找算法总结(暴力匹配、KMP 算法、Boyer-Moore 算法和 Sunday 算法)

    字符串匹配是字符串的一种基本操作:给定一个长度为 M 的文本和一个长度为 N 的模式串,在文本中找到一个和该模式相符的子字符串,并返回该字字符串在文本中的位置. KMP 算法,全称是 Knuth-Mo ...

  6. KMP字符串查找算法

    #include <iostream> #include <windows.h> using namespace std; void get_next(char *str,in ...

  7. Sunday算法(字符串查找、匹配)

    字符串查找算法中,最著名的两个是KMP算法(Knuth-Morris-Pratt)和BM算法(Boyer-Moore).两个算法在最坏情况下均具有线性的查找时间.但是在实用上,KMP算法并不比最简单的 ...

  8. 数据结构与算法--Boyer-Moore和Rabin-Karp子字符串查找

    数据结构与算法--Boyer-Moore和Rabin-Karp子字符串查找 Boyer-Moore字符串查找算法 注意,<算法4>上将这个版本的实现称为Broyer-Moore算法,我看了 ...

  9. 字符串匹配算法(KMP)

    字符串匹配运用很广泛,举个简单例子,我们每天登QQ时输入账号和密码,大家有没有想过账号和密码是怎样匹配的呢?登录需要多长时间和匹配算法的效率有直接的关系. 首先理解一下前缀和后缀的概念: 给出一个问题 ...

  10. 数据结构与算法--KMP算法查找子字符串

    数据结构与算法--KMP算法查找子字符串 部分内容和图片来自这三篇文章: 这篇文章.这篇文章.还有这篇他们写得非常棒.结合他们的解释和自己的理解,完成了本文. 上一节介绍了暴力法查找子字符串,同时也发 ...

随机推荐

  1. SciTech-Search-Bing.com 搜索API:{Web/ Custom / News / Autosuggest / Cognitive / Entity+Visual+Video+LocalBusiness / SpellCheck }: https://www.microsoft.com/en-us/bing/apis/bing-web-search-api

    Azure: https://docs.microsoft.com/python/api/overview/azure/cognitive-services https://github.com/Az ...

  2. SicTech-Math-Stolz-Cesaro Theorem + L'Hopital Rule

    https://math.stackexchange.com/questions/109069/stolz-cesàro-theorem?noredirect=1 https://planetmath ...

  3. .NET 使用 DocNET 库快速高效的操作 PDF 文档

    前言 PDF 文档,作为日常工作中不可或缺的文档格式,广泛应用于各类场景.今天我们来讲讲在 .NET 中使用 DocNET 库快速高效的操作 PDF 文档. 项目介绍 DocNET 是一个基于 .NE ...

  4. C语言Tips

    Tips 零. 写在前面 'a'(字符常量)和"a"有区别 调试时:监视里面输入(type()[number])begin type,类型 number,查看数量 begin,起始 ...

  5. Go动态感知资源变更的技术实践,你指定用过!

    最近在倒腾"AI大模型基础设施", 宏观目标是做一个基于云原生的AI算力平台,目前因公司隐私暂不能公开宏观背景和技术方案, 姑且记录实践中遇到的一些技能点. 前文已经记录了第1步: ...

  6. Casbin荣获2021年度“科创中国”开源创新榜优秀开源产品!

    为了响应党中央.国务院高度重视开源软件发展的需求(国家软件发展战略将培育开源生态作为重点任务,国民经济和社会发展"十四五"规划和2035年远景目标纲要明确提出支持开源社区等创新联合 ...

  7. 理解 SOLID 原则:编写更简洁的 JavaScript 代码

    编写简洁.可维护的代码是构建可扩展应用的关键.由罗伯特·C·马丁(Bob 大叔)提出的 SOLID 原则,是五条核心设计准则,能帮助开发者更好地组织代码.减少漏洞,并降低后续修改的难度. 本文将逐一拆 ...

  8. MySQL数据库Binlog解析工具--binlog2sql

    概述作为DBA,binlog2sql是一项必须掌握的工具.binlog2sql是一个开源的Python开发的MySQL Binlog解析工具,能够将Binlog解析为原始的SQL,也支持将Binlog ...

  9. Java 反射简单入门

    Java 反射简单入门 目录 Java 反射简单入门 Java反射介绍 Java反射的作用 反射结构分析 获取Class对象 Java代码在计算机中的三个阶段 三个阶段获取Class对象的方法 Cla ...

  10. LiveQing - 直播、点播高性能流媒体服务多级安全播控多清晰度视频转码

    LiveQing云平台直播点播流媒体服务: 一站式视频点播和直播解决方案,提供多清晰度转码,高性能RTMP流媒体服务,多种格式流分发服务,播放集成,将设备的推流进行无插件的互联网直播,提供了多层级的安 ...