kmp匹配算法

在讲解kmp算法之前,先讨论下一般的模式匹配算法

先看下代码

int index(string s,string t,int pos)
{
	
	for (int i=pos;i<s.length()&&i+t.length()<=s.length();i++){
		int flag=1;
		for (int j=0;j<t.length();j++){
			if (s[i+j]!=t[j]){
				flag=0;
				break;
			}
		}
		if (flag){
			return i+1;
		}
	}
	return 0;
}

返回的是b串第一次在a串的pos位置后出现的位置

可以显而意见的是复杂度为O(n*m)

在a串和b串为如下的形式的时候时

a:0000000001

b:001

你会发现b串在a串中进行匹配的时候,要到最后一次才会匹配成功,而前面的匹配很多都做了无用功,i指针根本就没必要回朔


kmp算法

kmp算法巧妙的解决了这个问题

将问题的复杂度下降到O(n+m)

那么它是怎么实现的呢?

先给大家看几个图示


当a串与b串进行比较的时候,可能在分别在i和j的位置的时候匹配失败了

这个时候如果是一般匹配方法就会i指针回朔到b1+1位置,而j则会回朔到b2位置

但是如果仔细观察下你会发现(b1,i-1)和(b2,j-1)是完全匹配的,而且你也可能会发现可能在b串中本身就存在(b2,s)完全等于(k,j-1)的情况,那么这个时候何不直接把j回朔到s+1的位置呢?

如下图所示



这样算法复杂度就可以得到很好的优化

但是如何实现以上过程呢

这里引进next数组

next[i]:=如果匹配失败了j指针应该滑向的位置

也可以理解成最大的一个值使得对于b串而言(0,next[i]-1)完全等于(j-next[i],j-1)

则可以定义如下

next[i]=-1,i==0

next[i]=k,,0<k<j,其中"p0p1p2p3...pk-1"="pj-kpj-k+1pj-k+2...pj-1"

next[i]=0,其余的情况,其实也就是b串0到j-1的位置上没有前后相互匹配的情况


那么只要求出next数组的值,就万事大吉了

下面给出代码

void getNext(char *p,int *next)
{
    int j,k;
    next[0]=-1;
    j=0;
    k=-1;
    while(j<strlen(p)-1)
    {
        if(k==-1||p[j]==p[k])    //匹配的情况下,p[j]==p[k]
        {
            j++;
            k++;
            next[j]=k;
        }
        else                   //p[j]!=p[k]
            k=next[k];
    }
}
int KMPMatch(char *s,char *p)    //如果不存在匹配项,则返回-1,否在返回开始的匹配位置
{                                //p匹配到s,s必须比p长
    int next[100];
    int i,j;
    i=0;
    j=0;
    getNext(p,next);
    while(i<strlen(s))
    {
        if(j==-1||s[i]==p[j])
        {
            i++;
            j++;
        }
        else
        {
            j=next[j];       //消除了指针i的回溯
        }
        if(j==strlen(p))
            return i-strlen(p);
    }
    return -1;
}

写法二,思路都是一样的,只是代码习惯的不同。。

#include <bits/stdc++.h>
using namespace std;
#define maxn 105
int next[maxn];

void get_next(string b)
{
	next[0]=-1;
	int j=0;
	for (int i=1;i<b.length();i++){
		while(j>0&&b[i]!=b[j]) j=next[j];
		if (b[i]==b[j]) next[i]=j++;
	}
}

int kmp(string a,string b,int pos)
{
	memset(next,0,sizeof(next));
	get_next(b);
	
	int j=0;
	for (int i=pos;i<a.length();i++){
		while(j>0&&a[i]!=b[j]) j=next[j];
		if (a[i]==b[j]) j++;
		if (j==b.length()){
			return i-b.length()+1;
		}
	}
	return -1;
}

int main()
{
	string a,b;
	while(cin>>a>>b){
		printf("%d\n",kmp(a,b,0));
	}
	return 0;
}


<think>好的,用户现在想了解KMP匹配算法的思路,特别是它的工作原理和实现思路。之前用户问过链表转红黑树的问题,这次转向字符串匹配算法,可能是在学习数据结构与算法,或者是准备面试需要。需要先确认用户的基础,但根据用户的问题,应该已经对基础的字符串匹配(比如暴力匹配)有所了解,现在想优化效率。 首先,回忆KMP算法的核心点。KMP的关键在于利用前缀函数(部分匹配表)来避免回溯主串指针,从而将时间复杂度从O(mn)降到O(m+n)。需要解释清楚为什么需要这个前缀函数,以及如何构建它。 用户提供的引用中有提到前缀函数,引用1和2中的代码示例是C++的实现,可能用户看过这些代码但需要更清晰的解释。引用3提到Python实现,可能用户对Python更熟悉,但问题中没有明确,所以回答中可能需要兼顾不同语言的逻辑,但重点放在思路而非具体代码。 用户的问题集中在思路,所以需要分步骤讲解。先对比暴力匹配的缺点,引出KMP的优势。然后分两部分:构建next数组(前缀表)和使用next数组进行匹配。 需要注意术语的一致性,比如部分匹配表、前缀表、next数组其实是同一个概念的不同叫法。需要统一使用一个术语,比如next数组,避免混淆。 在解释构建next数组时,要说明最长公共前后缀的概念,以及如何通过递推的方式计算每个位置的值。这里可能需要举例,比如模式串“ABABC”,逐步计算每个位置的next值,这样用户能更直观理解。 然后,在匹配过程中,当发生不匹配时,如何利用next数组跳过不必要的比较。例如,主串和模式串在某个位置不匹配时,模式串指针回退到next[j]的位置,而不是从头开始,主串指针不回溯。 用户可能存在的疑问包括:为什么next数组能减少比较次数?如何证明这个方法的正确性?不过根据用户的问题,可能不需要深入数学证明,而是更关注实现思路。 另外,用户提供的引用中有提到AC自动机,可能用户之后会学习更复杂的多模式匹配,但目前只需聚焦KMP。需要避免引入过多复杂概念,保持回答的针对性。 在实现部分,需要结合引用中的C++代码,解释关键步骤。例如,next数组的构建是通过双指针i和j,其中i从2开始,j从0开始,逐个比较字符,如果不匹配则回退j到next[j],直到匹配或j为0。匹配时j递增,然后记录next[i] = j。这个过程需要详细拆解,可能用例子说明。 同时,用户可能对代码中的一些细节有疑问,比如为什么数组从1开始(引用2中的代码),这在C++中可能是为了操作方便,但需要指出在一般实现中可以从0开始,视语言而定。 在应用场景部分,需要强调KMP适合重复使用同一模式串多次匹配的情况,因为构建next数组的时间可以在多次匹配中分摊。如果只匹配一次,可能暴力法更简单,但多次时KMP更优。 最后,相关问题需要引导用户深入,比如next数组的优化(如KMP的优化版)、与BM算法的对比,或者实际应用案例(如文本编辑器中的查找功能)。这些扩展问题可以帮助用户进一步学习。 总结回答结构:先介绍KMP解决的问题,对比暴力法;然后分步骤讲解前缀函数(next数组)的构建和使用;给出代码示例(可能用Python或简化版C++);最后讨论应用和优化。确保每一步都有逻辑衔接,用例子辅助理解,避免过于抽象。</think>--- ### **KMP算法核心思路** **目标**:在字符串匹配中,通过预处理模式串(Pattern)生成**前缀表(next数组)**,避免主串(Text)指针回溯,将时间复杂度从暴力法的$O(mn)$优化至$O(m+n)$[^1][^2]。 --- #### **一、暴力匹配的缺陷** 暴力匹配(Brute-Force)逐字符比较,当出现不匹配时,主串和模式串的指针均回溯,导致重复比较: ```python # 暴力匹配示例 def brute_force(text, pattern): n, m = len(text), len(pattern) for i in range(n - m + 1): j = 0 while j < m and text[i+j] == pattern[j]: j += 1 if j == m: return i return -1 ``` **问题**:主串指针`i`频繁回溯(如`i++`),导致多次重复匹配。 --- #### **二、KMP算法核心改进** ##### **1. 前缀表(next数组)** - **定义**:`next[j]`表示模式串`P[0...j]`的**最长公共前后缀长度**(即前缀与后缀的最大匹配长度)[^1]。 - **示例**: | 模式串 | `A` | `B` | `A` | `B` | `C` | |--------|-----|-----|-----|-----|-----| | next | 0 | 0 | 1 | 2 | 0 | ##### **2. 匹配过程优化** - **主串指针不回溯**:当`Text[i]`与`Pattern[j]`不匹配时,模式串指针`j`回退至`next[j-1]`,主串指针`i`保持不动。 - **关键逻辑**:利用已匹配部分的信息,跳过不可能成功的比较。 --- #### **三、实现步骤详解** ##### **1. 构建next数组** ```python def build_next(pattern): next = [0] * len(pattern) j = 0 # 前缀末尾指针 for i in range(1, len(pattern)): # i为后缀末尾指针 # 不匹配时回退j while j > 0 and pattern[i] != pattern[j]: j = next[j-1] # 匹配时j前进 if pattern[i] == pattern[j]: j += 1 next[i] = j return next ``` - **时间复杂度**:$O(m)$,模式串长度$m$。 ##### **2. 使用next数组匹配** ```python def kmp(text, pattern): next = build_next(pattern) j = 0 # 模式串指针 for i in range(len(text)): # 不匹配时回退j while j > 0 and text[i] != pattern[j]: j = next[j-1] # 匹配时j前进 if text[i] == pattern[j]: j += 1 # 完全匹配 if j == len(pattern): return i - j + 1 return -1 ``` - **时间复杂度**:$O(n)$,主串长度$n$。 --- #### **四、关键逻辑示例** **场景**:主串`Text="ABABABC"`,模式串`Pattern="ABABC"`,其`next=[0,0,1,2,0]`。 1. **首次不匹配**: - `Text[4]='A'` vs `Pattern[4]='C'` - `j`回退至`next[3]=2`,继续比较`Text[4]`与`Pattern[2]` 2. **成功匹配**: - `j`推进至`5`,返回位置`i-j+1=4-5+1=0`(示例中的模式串实际匹配位置需调整)[^2]。 --- #### **五、应用场景** 1. **文本编辑器查找**:需快速定位所有匹配位置(如VSCode的Ctrl+F)[^3]。 2. **生物信息学**:DNA序列模式匹配。 3. **网络协议解析**:高效匹配特定数据包头。 --- ### **相关问题** 1. **如何手动计算模式串`"ABCDABD"`的next数组?** 2. **KMP算法与Boyer-Moore算法在性能上有何差异?** 3. **前缀表如何优化为更高效的`nextval`数组?**[^1] 4. **KMP算法在中文或Unicode场景下需要哪些调整?**[^3] --- **提示**:理解KMP的核心在于**最长公共前后缀**的递推逻辑,建议通过手动模拟`build_next()`过程加深理解[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值