字符串 KMP

Chapter 7. 字符串  KMP

Sylvia's  I.

  我们储存的字符串从标号1开始

  关于Next数组的作用,贴一个博客:http://www.ruanyifeng.com/blog/2013/05/Knuth–Morris–Pratt_algorithm.html 

 

  那么该如何计算Next

 代码是最好的语言.

void GetNext(char p[],int lenp){//p[]是模式串,lenp是p[]的长度
    int j=0;//j记录的是已经匹配的字符数,初始并没有匹配
    Next[1]=0;//对于任意单独的一个字符,它前缀和后缀都为空集,共有元素的长度为0
    for (int i=2;i<=lenp;i++){
        while (j&&p[i]!=p[j+1]) j=Next[j];//注释①
        if (p[j+1]==p[i]) j++;//如果下一位匹配成功,那么j(已经匹配的字符数加一)
        Next[i]=j;//注释②
    }
}

注释①:计算Next数组实质上是p的自我匹配,但是模式串是不断变化的,为什么这么说呢,比如p=‘ABABABCA’

       “主串”              “模式串”( i 指向的是这里的”模式串“)

i=2 ABABABCA  BABABCA  Next[2]=0 失配

i=3 ABABABCA  ABABCA    Next[3]=1 ,然后会匹配“模式串”中第二位和”主串“中的第二位,i=4 Next[4]=2,然后匹配第三位i=5 Next[5]=3,匹配第四位,i=6,Next[6]=4,匹配第五位,失配

i=7 ABABABCA  CA            Next[6]=0. 失配

i=8 ABABABCA  A              Next[7]=1. 

最终Next={0,0,1,2,3,4,0,1}

在匹配的过程中,一旦失配,模式串就会变化,当然这只是我们将过程形象化而已,程序中的实现中是一旦失配,是将j变化从而实现”模式串“的变化,j跳转至当前字符之前字符串的最长公共前缀后缀最后一个字符,直至j=0或者下一位匹配成功,有点绕……

比如,ABDABCD Next={0,0,0,1,2,0,0} 假如此时j=5,指向B,此时下一位C失配了,我们就将j就跳转至此字符(B)之前字符串(ABDAB)的最长公共前缀后缀(AB)最后一个字符(B),那么j就指向了第一个B,对于这句话的赋值就是j=Next[j],那么赋值后也就是j=2了,如果下一位继续失配,那么继续跳转。

 

注释②:当前Next就是已经匹配的位数,就是j

Sylvia's II.

   关于KMP

void KMP(char t[],int lent,char p[],int lenp){//t主串,p模式串    int j=0;//j指向p,表示已经匹配的位数
    for (int i=1;i<=lent;i++){//i指向t
        while (j&&p[j+1]!=t[i]) j=Next[j];//如果失配,跳转
        if (p[j+1]==t[i]) j++;//匹配成功一位,j+1
        if(j==lenp) {//如果匹配完成
            printf("%d\n",i-lenp+1);//输出位置
            j=Next[j];//跳转,继续匹配
        }
    }
}

 

Sylvia’s III.

完整代码: 

//洛谷p3375
//给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置。
//并输出前缀数组Next
//标号1开始 #include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #include<cmath> using namespace std; #define debug(x) cerr<<#x<<'='<<x<<endl #define MAXN1 1234567 #define MAXN2 1234 int Next[MAXN2]; char target[MAXN1],pattern[MAXN2]; int lenp,lent; void GetNext(char p[],int lenp){ int j=0; Next[1]=0; for (int i=2;i<=lenp;i++){ while (j&&p[i]!=p[j+1]) j=Next[j]; if (p[j+1]==p[i]) j++; Next[i]=j; } } void KMP(char t[],int lent,char p[],int lenp){// int j=0; for (int i=1;i<=lent;i++){ while (j&&p[j+1]!=t[i]) j=Next[j]; if (p[j+1]==t[i]) j++; if(j==lenp) { printf("%d\n",i-lenp+1); j=Next[j]; } } } int main (){ scanf("%s",target+1); scanf("%s",pattern+1); lenp=strlen(pattern+1); lent=strlen(target+1); GetNext(pattern,lenp); KMP(target,lent,pattern,lenp); for (int i=1;i<=lenp;i++){ printf("%d ",Next[i]); } return 0; }

 

 再贴一个从标号0开始的

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
#define debug(x) cerr<<#x<<'='<<x<<endl
#define MAXN1 1000006
#define MAXN2 1003

int Next[MAXN2];
char target[MAXN1],pattern[MAXN2];
int lenp,lent; 

void GetNext(char p[],int lenp){
    int j=0;
    Next[1]=0;
    for (int i=1;i<lenp;i++){
        while (j>0&&p[j]!=p[i]) j=Next[j];
        if (p[i]==p[j]) j++;
        Next[i+1]=j; //这里的不同,但是Next数组都是从1开始存储的 
    }
}

void KMP(char t[],int lent,char p[],int lenp){
    int j=0;
    for (int i=0;i<lent;i++){
        while (j>0&&p[j]!=t[i]) j=Next[j]; 
        if (t[i]==p[j]) j++;
        if (j==lenp){
            printf("%d\n",i-lenp+2);//输出也不同 
            j=Next[j];
        }
    }
}

int main (){
    scanf("%s",target);
    scanf("%s",pattern);
    lenp=strlen(pattern);
    lent=strlen(target);
    GetNext(pattern,lenp);
    KMP(target,lent,pattern,lenp);
    for (int i=1;i<=lenp;i++){
        printf("%d ",Next[i]);
    }
    return 0;
}

 

 


 

 

不负我心

三毛

心之如何

有似万丈迷津

遥亘千里

其中并无舟子可以渡人

除了自渡

他人爱莫能助

 


 

Sylvia

二零一七年六月一日

转载于:https://www.cnblogs.com/jasmine-lee/p/6914362.html

<think>好的,我现在要帮助用户了解KMP算法和进制哈希在字符串处理中的应用及实现。首先,我需要回顾一下这两种算法的基本概念和原理,确保自己理解正确。 首先,KMP算法。记得KMP主要用于字符串匹配,其核心思想是利用已经部分匹配的信息来避免不必要的回溯。传统暴力匹配(BF算法)在每次失配时都会将主串和模式串的指针回退,导致时间复杂度较高,而KMP通过next数组优化了这一过程。next数组存储的是模式串每个位置的最长公共前后缀长度,这样在失配时,模式串可以滑动到next[j]的位置,主串指针不需要回溯。这样时间复杂度从O(mn)降到O(m+n)。需要详细解释next数组的构建过程,可能还要举例说明,比如引用中的例子next数组为01012的情况。 然后是进制哈希,也就是字符串哈希。根据引用中的描述,进制哈希将字符串转换为一个数值,通常使用K进制数,再转换为十进制作为哈希值。这种方法可以在O(1)时间内比较子串是否匹配,前提是预处理前缀哈希值。比如RK算法就是BF算法的改进,利用哈希来快速比较子串,减少逐个字符比较的次数。需要注意哈希冲突的问题,可能需要双哈希或者其他方法来降低冲突概率。 接下来要考虑两者的应用场景。KMP适用于需要高效匹配且处理频繁失配的情况,比如文本编辑器的查找功能,或者生物信息学中的DNA序列匹配。而进制哈希适合需要快速比较多个子串的场景,比如在大量文本中查找多个模式串,或者处理回文子串问题,比如使用前后缀哈希判断回文。 在实现方面,KMP的关键是构建next数组。这通常通过双指针法预处理模式串,初始化next[0]=0,然后遍历模式串,比较前后缀。当字符匹配时,指针同时后移,否则回退到前一个next值。这个过程需要仔细处理边界条件,避免错误。 进制哈希的实现则包括选择适当的基数和模数,比如使用大质数作为模数减少冲突。预处理前缀哈希数组和幂数组,以便快速计算任意子串的哈希值。例如,对于字符串s,前缀哈希数组hash[i]表示前i个字符的哈希值,那么子串s[l..r]的哈希值可以通过hash[r] - hash[l-1]*base^(r-l+1)来计算,再取模。需要注意处理负数的情况,可以通过加上模数再取模来调整。 优化方面,KMP的优化可能包括改进next数组的计算,比如进一步优化next数组为nextval数组,减少不必要的匹配。而进制哈希的优化可能涉及双哈希,即使用两个不同的基数和模数生成两个哈希值,从而降低冲突概率。此外,预处理幂数组可以避免重复计算base的幂次,提高效率。 现在需要结合用户提供的引用内容。例如,引用3提到next数组的构造方法,引用2和5解释了进制哈希的原理,引用4提到了处理负数的情况,虽然那个例子是关于数对的,但可能对哈希处理负数有参考意义。不过字符串哈希通常处理的是字符,字符一般不会有负数,所以可能不需要处理负数,但如果是自定义的哈希映射,可能需要考虑。 在回答结构上,应该先分点讲解KMP和进制哈希的原理、实现步骤,然后讨论它们的应用场景和优化方法。同时,根据用户的问题,需要包括两者的实现和优化,所以可能需要给出代码示例或者伪代码,比如KMP的next数组构建函数,以及进制哈希的预处理和查询方法。 最后,生成相关问题时要围绕KMP和进制哈希的不同方面,比如比较两者的优缺点,具体实现中的细节,如何解决冲突,实际应用案例等。 需要确保LaTeX格式正确,行内公式用$...$,独立公式用$$...$$,并且正确引用参考资料中的内容,比如引用3关于next数组的解释,引用2和5关于进制哈希的设计。</think>### KMP算法与进制哈希的应用及实现 #### 一、KMP算法原理与实现 **核心思想**:通过预处理模式串生成$next$数组,记录最长公共前后缀长度,避免主串指针回溯。 **时间复杂度**:预处理$O(m)$,匹配$O(n)$,总复杂度$O(m+n)$[^3]。 **$next$数组构建步骤**(以模式串$B$为例): 1. 初始化$next[0]=0$,双指针$i=1, j=0$。 2. 若$B[i] = B[j]$,则$next[i] = j+1$,$i$和$j$均后移。 3. 若不等且$j>0$,则$j=next[j-1]$;否则$next[i]=0$,$i$后移。 示例:模式串$B=ababc$的$next$数组为$[0,0,1,2,0]$[^3]。 ```python def build_next(pattern): next_array = [0] * len(pattern) j = 0 for i in range(1, len(pattern)): while j > 0 and pattern[i] != pattern[j]: j = next_array[j-1] if pattern[i] == pattern[j]: j += 1 next_array[i] = j return next_array ``` #### 二、进制哈希原理与实现 **核心思想**:将字符串映射为$K$进制数,例如字符串$s=abc$的哈希值可表示为: $$hash(s) = a \times K^2 + b \times K + c$$ 通过预处理前缀哈希和幂数组,快速计算任意子串的哈希值[^2][^5]。 **实现步骤**: 1. 选择基数$K=131$和模数$P=10^{18}+3$。 2. 预处理前缀哈希数组$hash$和幂数组$power$: $$hash[i] = hash[i-1] \times K + s[i]$$ $$power[i] = power[i-1] \times K \mod P$$ 3. 子串$s[l..r]$的哈希值为: $$hash(r) - hash(l-1) \times power[r-l+1] \mod P$$ **优化方法**: - **双哈希**:使用两组不同的$(K,P)$降低冲突概率。 - **预处理幂数组**:避免重复计算$K$的幂次[^5]。 #### 三、应用场景对比 | **算法** | **应用场景** | **优势** | |------------|-----------------------------------------------|---------------------------| | **KMP** | 单模式串匹配(如文本搜索、DNA序列分析) | 保证线性时间复杂度 | | **进制哈希** | 多模式串匹配、回文判断(如最长回文子串问题) | 支持快速子串比较,复杂度低 | #### 四、优化方法对比 | **优化方向** | **KMP** | **进制哈希** | |--------------------|-----------------------------------|-------------------------------| | **预处理优化** | 改进$next$数组为$nextval$数组[^3] | 双哈希降低冲突 | | **匹配效率提升** | 主串指针不回溯 | 哈希比较替代逐字符比较[^2] |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值