KMP

模板题链接
搞了好久可算明白了


先放个例子:

text a b c x a b c d a b x a b c d a b c d a b c y
num 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
pattern a b c d a b c y
num 0 1 2 3 4 5 6 7

正常匹配算法过程:

指针 k 逐个遍历 text 字符
对于每个 k ,定义 i j,每次初始时,i = 0,j = 0

while( k + i < text.length() && j < pattern.lenth() )
{
    if( text[ k + i ] == pattern[ j ] )
    {
          if ( j == pattern.lenth() - 1 ) res.push_back( k )
          else
          {
              i++;j++;
          }
    }
}

KMP流程

按照kmp的流程:

  1. 两个指针,i 为 text 指针, j 为 pattern 指针

  2. i 、 j 位置的字符相同,则都往前

  3. 当我们匹配完text与pattern的0-2后,发现两者的 3 位置不同,那就寻找 pattern 中,j - 1(即2)位往前整一串是否有相同的前后缀,没有,则 j = 0, 即从头开始重新匹配 ,此时 text[ i ] != pattern[ j ],即 text[ i ] != pattern[ 0 ], 进行 i ++

  4. 重复2 ,也是直到 text[ i ] != pattern[ j ]

  5. 此时 i = 10 , j = 6 ,寻找 pattern 中,j - 1(即5)位往前整一串是否有相同的前后缀,有 , 即“ ab ”

  6. 关键: 因为(1)此时 i j 不匹配, (2)前面的都匹配,(3)前面存在相同前后缀
    直接把 j 移动到 前缀的后一位,继续进行 i j 的匹配。

解释如下:
因为(1) 需要重新匹配
因为(2)(3),可得: text的后缀 就是 pattern的前缀

直接把 j 移动到 pattern中的前缀的 后面一位 ,就相当于完成了正常算法中从 k 开始一个个比较这里相同前后缀的 text[ i ] 和 pattern[ j ] 的过程

  1. 重复以上步骤,直到 j == pattern.length(), 此时完成了整串 pattern 的查找,i - j + 1 即为查找到的 pattern 在 text 中出现的位置

  2. 把 j = 0, i + 1,按以上步骤继续查找下一个位置


接下来是如何记录每次匹配不成功的时候 j 要退回到哪里

很简单,用上面的方法,让 pattern 自己匹配自己就行

解释如下:
上面的是在text中找pattern
那如果是在pattern中找pattern呢
是不是 每次匹配失败的位置 就是 前缀的后一位
但不一样的是,即使匹配成功也要进行记录,
因为我们要的是在pattern 每个位置上匹配失败时要跳转到哪里

参考代码
#include <bits/stdc++.h>
typedef long long int LL;
using namespace std;
const int N=1e6+10,MOD=1e9+7;

string string_tobe_search,patern;
LL border[N];
vector<int> res;

void kmp_patern_self_match()
{
	border[0]=0;
	int j=0;
	for(int i=1;i<patern.size();i++)
	{
		if(patern[i]==patern[j])
		{
			border[i]=j+1;
            //i为后缀末尾,匹配失败时,匹配失败的位置就是i + 1,
            //j 为 前缀末尾 , j + 1即为匹配失败后,pattern指针要跳转到的位置
			j++;
		}
		else
		{
			while(j>0&&patern[i]!=patern[j]) j=border[j-1];
			if(patern[i]==patern[j]) border[i]=j+1,j++;
			else border[i]=0;
		}
	}
}

void kmp_find_patern_position()
{
	int j=0;
	for(int i=0;i<string_tobe_search.size();i++)
	{
        //如果匹配
		if(string_tobe_search[i]==patern[j])
		{
			j++;
			if(j==patern.size())
			{
				res.push_back(i-j+1);
				//这里 +1 是因为题目要求输出位置从1开始、
				//如果要求从0开始则去掉 +1
				j=border[j-1];
			}
		}
		else//如果不匹配
		{
            //直到 匹配 或者 j 回到 0
			while(j>0&&string_tobe_search[i]!=patern[j]) j=border[j-1];
			if(string_tobe_search[i]==patern[j]) j++;
		}
	}
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	
	cin>>string_tobe_search>>patern;
	
	kmp_patern_self_match();
	kmp_find_patern_position();
	
	for(int pos: res) cout<<pos<<" ";
	return 0;
}

posted @ 2025-09-26 13:25  石磨豆浆  阅读(9)  评论(0)    收藏  举报