【算法模板】轻松学会KMP算法

目录

0.前言

 1. 视频理解KMP的流程

 2.代码实现

3.结束语


0.前言

KMP算法是在字符串中寻找字串的算法,时间复杂度为O(n)。

KMP算法中有两个关键因素:

  1. next数组
  2. 匹配机制

 

 1. 视频理解KMP的流程

 由于想把最好的资源分享给大家(绝对不是作图水平太低!,读者们可以在下面的视频中先行了解KMP算法,这也是作者最先了解KMP思想的视频:

https://www.bilibili.com/video/BV1AY4y157yL/?spm_id_from=333.337.search-card.all.click&vd_source=936e3852c850b5e76e4c4e91daa5dd95

如果大家看完视频后还在看这篇文章,那么下面就是对于KMP算法的具体实现。


 

 2.代码实现

 p1建议使用“cbcbc”这个字符串来带入理解。

 p2建议使用“cbc”这个字符串来带入理解。

开始之前,我们需要知道字符串前后缀子串

【算法模板】轻松学会KMP算法_第1张图片

像这样,如果是奇数,那么前3个字符称为字符串的前缀,后面的CBA则是后缀。

ne数组的原理就是从第二个字符开始往后找。

【算法模板】轻松学会KMP算法_第2张图片

 字符串AB中,前缀A不等于前缀B,于是B在next数组中存的是0;

【算法模板】轻松学会KMP算法_第3张图片

字符串ABC中,前缀A和 后缀C不同,于是C在next数组中存的是0;

以此类推~~~

char* my_strstr(const char* p1,const char* p2)
{
    //在 s 字符串中寻找 p 字符串
	char* s = p1 - 1; char* p = p2 - 1;

    //next数组 ne
	int* ne = (int*)malloc(strlen(p2) * 4);
    
    //因为首字符没有前后缀子串
	ne[1] = 0;

    //next数组的创建
	for (int i = 2, j = 0; p[i]; i++)
	{
        // j 代表了 目前正在匹配的字符的前一个字符 的下标。
		while (j && p[j + 1] != p[i])
		{
            //如果匹配失败,那么 j 作为下标将会回到 ne[j] 的位置
			j = ne[j];
		}

        //匹配成功,证明有相同的前后缀字串,j代表了相同的子串长度
		if (p[i] == p[j + 1])
		{
			j++;
		}

        //next数组中存放i位置时,相同的前后缀子串长度
		ne[i] = j;
	}

    //寻找字串
	for (int i = 1, j = 0; s[i]; i++)
	{
        // j 代表了模板串的 已经匹配成功的 长度

        //匹配不成功就回撤继续匹配
		while (j && p[j + 1] != s[i])
		{
			j = ne[j];
		}

        //匹配成功,已经匹配成功的长度加1
		if (p[j + 1] == s[i])
		{
			j++;
		}
        
        //如果模板串已经匹配完最后一个字符,那么停止匹配,返回子串首字符地址
		if (j == strlen(p2))
		{
            // i - strlen(p2) 子串首字符距离p2的长度
			return p1 + i - strlen(p2);
		}
	}
	return NULL;
}

其实写在函数里多少有点影响KMP算法的功能,因为这个算法可以找到多处子串的起始位置。

所以可以考虑改造一下或则写在main函数中。

写在main函数中时,通常用字符数组来存字符串,这里说一下,字符串最好从下标为1的地址开始存。

#include 
#include 
using namespace std;

const int N = 1000100;

//next数组
int ne[N];

//在p中寻找子串s
char s[N],p[N];

//字符串s的长度为m,字符串p的长度为n
int m,n;

int main()
{
    cin >> n >> p + 1 >> m >> s + 1;
    //创建next数组
    for(int j = 0, i = 2; i <= n; i++)
    {
        while(j && p[j + 1] != p[i])
        {
            j = ne[j];
        }
        if(p[i] == p[j+1])
        {
            j++;
        }
        ne[i] = j;
    }
    //开始匹配
    for(int i = 1,j = 0; i <= m; i++)
    {
        while(j && s[i] != p[j + 1])
        {
            j = ne[j];
        }
        if(s[i] == p[j + 1])
        {
            j++;
        }
        if(j == n)
        {
            //返回子串下标
            printf("%d ",i - n);
            j = ne[j];
        }
    }
    return 0;
}

 

3.结束语

KMP算法是比较难理解的一个算法,要多琢磨,建议数组储存数据时从下标1开始储存,这样会方便很多,本代码中让p1和p2减一也是为了实现 储存数据时从下标1开始储存。

另外,能用KMP算法解决的问题,同样也可以使用字符串哈希算法解决。有兴趣可以去了解一下。

你可能感兴趣的:(算法模板,算法)