zoukankan      html  css  js  c++  java
  • 浅谈字符串匹配的KMP算法

    今天跟qs聊了会,她放出了我的小时的照片,毕竟黑历史谁都有

    然后,Singercoder 极限卡篮,还有就是,我又掉 rating 了,我也想去 NOI

    PS: Singercoder 掉青 2020/3/7

    update: 2020/3/23 相应 Singercoder 所做的笔记

    个人认为 Singercoder 的笔记写的是真的清晰 ,但无奈码风过于毒瘤了

    KMP

    前言

    KMP 算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。

    说句闲话

    先说一下字符串匹配的定义,就是一个给你一个主串 S 一个模式串 T 然后求 T 在 S 里出现的每个位置

    没有学KMP之前,我会两种字符串匹配算法,一个是朴素匹配,一个是有限自动机(不算是会,就是了解一下)但是这两个算法复杂度无疑可以卡成 (O(nm)), 是无法接受的,然而这闲的没事干的三人把字符串匹配做成了线性的 %%%。

    但是似乎在 oi 里的用处不是很大吧,我从来也没在题里用过他,除了板子题

    正文

    来介绍一种 Knuth-Morris-Pratt 算法 这个算法可以做到在 (O(n + m)) 时间内完成模式串和主串的匹配,利用了前缀的一些性质,用到了辅助函数 (pi)

    看算法的请往跳过,这里说的是原理

    关于模式的前缀函数

    这个算法的核心思想就在于此了,一个 (pi) 函数。它包含了模式与其自身的偏移的信息。这些信息在朴素匹配中没有利用到所以慢。

    考察一下朴素字符串匹配的操作过程,下图正是朴素里一个匹配中的情景

    我们可以看到,(q = 5) 个字符已经匹配成功了,那么我们知道的这 (q) 个字符可以为我们知道一些不必要的偏移了。在这个实例中,显然偏移为 s + 1 是无用的,因为第一个字符 (a) 将于文本匹配,但是该模式串的第二个字符 (b) 并不能于匹配。如下图所示,偏移 s' = s + 2 使模式串的前三个字符与后三个字符匹配

    前方高能!

    我们知道下面问题的 answer 是很有用的:

    假设模式串 (T[1...q]) 和主串 (S[s + 1.. S+q]) 匹配,(s') 是最小的偏移量, (s' > s) , 那么对于某些 (k < q) ,满足

    [T[1..k] = S[s'+1..s'+k] ]

    的最小偏移 (s' > s) 其中 (s' + k = s + q) 是什么?

    话句话说,已知 (T_qsqsupset S_{s+q}) ((asqsupset b) 表示 a 是 b 的后缀) 我们想要找到 (T_q) 的最长真前缀 k,也是 (S_{s+q}) 的后缀(找到 (k) 等价于写出了 (s') ) 显然的有 $s'= s + q - k $ .于是我们可以预先处理出这些 (k)(pi) 数组存起来.

    并且我们发现,求解这些信息就是一个 (T) 与其自身的匹配过程,下面来模拟一下过程以形象的说明.已知一个模式串 (T[1..m]) 前缀函数 (pi : {1...m} ightarrow{0...m - 1}) 满足

    [pi[i] = max(k:k<i且 T_k sqsupset Ti) ]

    下图给出了一个完整的 (pi) 函数

    我们求解这个数组无疑就是自身与自身的匹配过程

    代码实现

    给出一种很巧妙的方法,我们不妨先设 pi[0] = -1 这可以减少码量

    我们呢不妨假设 (pi) 已求出,现在要主串S与模式串T匹配

    具体的时候,因为实现的问题,我们无需记录偏移量 (s) ,而是维护两个指针 (i,j).

    for(i = 1; i <= lens; i++){
    	while(j > -1 && T[j+1] != S[i]) j = pi[j];
    	if(++j == lent) printf("%d
    ", i - lenb + 1);
    }
    

    可以理解为如果下一个字符不匹配那么就要做偏移了.直到一样或者无法偏移了

    $ pi$ 就是 (T) 与其本身的每个后缀的最长公共前缀.我们求解 $ pi$ 的过程就是 (T) 与其本身的的匹配(但是不要从第一个开始)

    特别地我们有一条引理 (pi[i] le pi[i - 1] + 1)

    所以程序写的十分巧妙.

    pi[0] = -1;
    for(int i = 1; i <= lenb; i++){
    	int j = pi[i - 1];
    	while(j > -1 && T[i] != T[j+1]) j = pi[j];
    	pi[i]=j+1;
    }
    

    exKMP

    PS:会补充的

    exKMP 解决的是这么一个问题,给你主串 S 和 字串 T,问题对于 S 的每个后缀 ,与 T 的最长公共前缀的长度

    我们先定义数组:

    [z[i] : 表示 T 的每个后缀与 T 的最长公共前缀的长度\ p[i] : 表示 S 的每个后缀与 T 的最长公共前缀的长度\ ]

    在这里,我们不妨设 z 已求出,我们的任务是求解 p

    在下面的表述方法中,对于一个字符串 S,记 (S_i)S 的第 i 个字符,(S_{l sim r})S 从第 l 个字符起到第 r 个字符结束形成的子串

    考虑从前向后地计算 p ,假设我们要计算 (p_i),那么由 (forall j in [1,i),p_i) 已算出

    l 是目前已经计算过的位置中,向由扩展的最长前缀的首字母的位置,即 l 满足 (p_l +l) 最大,记 r 为这个值,显然的有 (r ge i-1)

    我们在此时就开始分类 :

    part 1

    我们讨论 (r = i - 1) 的情况,我没在之前没有能利用的信息,于是,直接暴力匹配就好。

    part 2

    我们讨论 (r > i - 1) 的情况。这可就棘手了。当时没听懂 ,首先记 (j = i - l +1)

    故而我们在做 l 的时候,就知道 (S_{lsim i}= T_{1 sim j})

    于是,考虑我们要重复匹配的区间,其长度自然是 (r-i+1),比较它与 (z_j) 的关系,分两种情况讨论

    part 2.1

    (z_j<r-i+1) ,则有 (T_{1sim z_j}=T_{ j sim j+z_j-1}),又因 (S_{isim r}=T_{jsim j+(r-i+1)}),于是我们有 (S_{i_i+z_j-1}=T_{jsim j+z_j-1}=T_{1sim z_j}),而显然的有 (S_{i+z_j}=T_{j+z_j} ot= T_{z_j+1})

    于是有 (p_i=z_j=z_{i-l+1})

    part2.2

    照着前一个 能证明 (p_i ge r-i+1),后面不造故而暴力匹配

    然后做 l,r 的的更新即可

    z 数组的求解无非就是 T 本身的匹配,只要初始化 (z_1) 即可

    献上代码

    #define next z
    #define extand p
    
    void Get_Z(){
    	next[1] = lenb;
    	for(int i = 2, l = 0, r = 0; i <= lenb; ++i) {
    		if(i <= r) next[i] = min(next[i - l + 1], r - i + 1);//先看看
    		while(i + next[i] <= lenb && b[i + next[i]] == b[next[i] + 1]) ++next[i];//然后暴力
    		if(i + next[i] -1 > r) l = i, r = i + next[i] - 1;//再更新
    	}
    }
    
    void Get_P(){
    	for(int i = 1, l = 0, r = 0;i <= lena; ++i) {
    		if(i <= r) extand[i] = min(next[i - l + 1], r - i + 1);
    		while(i + extand[i] <= lena && a[i + extand[i]] == b[extand[i] + 1]) ++extand[i];
    		if(i + extand[i] - 1 > r) l = i, r = i + extand[i] - 1; 
    	}
    }
     
    
  • 相关阅读:
    爱情十二课,失恋后遗症
    爱情十三课,爱人的五功能
    爱情第八课,爱也是投资
    爱情第二课,择爱两大误区
    爱情十七课,吵架的原则
    MFC DC的获取
    MFC关于使用CArchive流输入产生的问题
    MFCCFileException类学习笔记
    MFC中指针的获取
    文字编辑和文字处理
  • 原文地址:https://www.cnblogs.com/zhltao/p/12556968.html
Copyright © 2011-2022 走看看