kmp匹配


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SRC_LEN 100
#define MDL_LEN 20

void makeNext(char * szModel, int * next, int len);
void match(char * szSrc, char * szModel, int * index);

int main(void)
{
    char src[SRC_LEN];
    char mdl[MDL_LEN];
    int  len;
    int  index;

    puts("请输入源字符串:");
    gets(src);
    puts("请输入模式字符串:");
    gets(mdl);

    len = strlen(mdl);
    match(src, mdl, &index);
    printf("%d", index);
    getchar();
    return 0;
}

/* 匹配函数。通过index返回相匹配的字符串首字符在源字符串中的位置,
   通过返回值返回相匹配的字符串首字符的地址。若无相匹配的字符串,则
   index为-1,并返回空指针NULL
 */
void match(char * szSrc,char * szModel, int * index)
{
    char * des;
    int    srcIndex = 0;
    int mdlIndex = 0;
    int srcLen = strlen(szSrc);
    int mdlLen = strlen(szModel);
    /* 创建next数组,并初始化 */
    int *next = (int *)malloc(mdlLen * sizeof(int));
    makeNext(szModel, next, mdlLen);
    
    /* 匹配主循环体 */
    while (mdlIndex < mdlLen && srcIndex < srcLen) {
        /* 若对应位置字符匹配则步进1,否则移动szModel */
        if (mdlIndex == -1 || szSrc[srcIndex] == szModel[mdlIndex]) {
            mdlIndex++; srcIndex++;
        } else {
            mdlIndex = next[mdlIndex];
        }
    }

    /* 若mdlIndex未达到串尾,表明szModel未完成匹配。否则即是完成匹配 */
    if (mdlIndex >= mdlLen) {
        *index = srcIndex - mdlLen;
    } else {
        *index = -1; 
    }
}

void makeNext(char * szModel, int * next, int len)
{
    int index = 0;
    int k = -1;
    next[0] = -1;

    /* 扫描szModel字符串以确定next数组 */
    while (index < len) {
        while (k >= 0 && szModel[index] != szModel[k])
            /* 若不匹配,则移动字符串,同match函数 */
            k = next[k];
        index++; k++;
        /* !!!ATTENTION!!! */
        if (szModel[index] == szModel[k]) {
            next[index] = next[k];
        } else {
            next[index] = k;
        }        
    }
}

<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、付费专栏及课程。

余额充值