• 扩展 KMP


    简介

    对于一个长度为 (n) 的字符串 (s),定义函数 (z[i]) 表示 (s)(s[i,n-1]) (即以 (s[i]) 开头的后缀)的最长公共前缀(LCP)的长度。(z) 被称作为 (s)Z 函数。特别地,(z[0]=[0])

    国外一般将计算该数组的算法称为 Z Algorithm,而国内则称其为 扩展 KMP

    样例

    下面若干样例展示了对于不同字符串的 Z 函数:

    • (z( ext{aaaaa})=[0,4,3,2,1])
    • (z( ext{aaabaab})=[0,2,1,0,2,1,0])
    • (z( ext{abacaba})=[0,0,1,0,3,0,1])

    线性算法

    在该算法中,我们从 (1)(n−1) 顺次计算 (z[i]) 的值((z[0]=0))。在计算 (z[i]) 的过程中,我们会利用已经计算好的 (z[0],…,z[i−1])

    对于 (i),我们称区间 ([i,i+z[i]−1])(i)匹配段,也可以叫 Z-box。

    举例:

    对于 (z( ext{abacaba})=[0,0,1,0,3,0,1]:)

    • (i=2) 时,匹配段为 ([2,2]),即字符 a
    • (i=4) 时,匹配段为 ([4,6]),即子串 abc

    算法的过程中我们维护右端点最靠右的匹配段。为了方便,记作 ([l,r])。根据定义,(s[l,r])(s) 的前缀。在计算 (z[i]) 时我们保证 (l≤i)。初始时 (l=r=0)

    在计算 (z[i]) 的过程中:

    • 如果 (i≤r),那么根据 ([l,r]) 的定义有 (s[i,r]=s[i−l,r−l]),因此 (z[i]≥min(z[i−l],r−i+1))。这时:
      • (z[i−l]<r−i+1),则 (z[i]=z[i−l])
      • 否则 (z[i−l]≥r−i+1),这时我们令 (z[i]=r−i+1),然后暴力枚举下一个字符扩展 ([i]) 直到不能扩展为止。
    • 如果 (i>r),那么我们直接按照朴素算法,从 (s[i]) 开始比较,暴力求出 (z[i])
    • 在求出 (z[i]) 后,如果 (z[i]>r),我们就需要更新 ([l,r]),即令 (l=i,r=i+z[i]−1)

    代码

    设小串(模式串)为 (B),大串(文本串)为 (A)

    本代码中 (z[0]=|B|)

    (z) 数组:

    void getZ()
    {
        z[0] = lenb;
        int now = 0;
        while (now + 1 < lenb && B[now] == B[now + 1])
            now++;
        z[1] = now;
        int p0 = 1;
        for (int i = 2; i < lenb; ++i)
        {
            if (i + z[i - p0] < p0 + z[p0])
            {
                z[i] = z[i - p0]; //第一种情况
            }
            else
            {
                now = p0 + z[p0] - i;
                now = max(now, 0);
                while (now + i < lenb && B[now] == B[now + i])
                    now++;
                z[i] = now;
                p0 = i;
            }
        }
    }
    

    (B)(A) 的每一个后缀的 LCP 长度数组:

    void exkmp()
    {
        int now = 0;
        while (now < lenb && now < lena && B[now] == A[now])
            now++;
        ext[0] = now;
        int p0 = 0;
        for (int i = 1; i < lena; ++i)
        {
            if (i + z[i - p0] < p0 + ext[p0])
                ext[i] = z[i - p0];
            else
            {
                now = p0 + ext[p0] - i;
                now = max(now, 0);
                while (now < lenb && now + i < lena && B[now] == A[now + i])
                    now++;
                ext[i] = now;
                p0 = i;
            }
        }
    }
    

    复杂度分析

    对于内层 while 循环,每次执行都会使得 (r) 向后移至少 (1) 位,而 (r<n−1),所以总共只会执行 (n) 次。

    对于外层循环,只有一遍线性遍历。

    总复杂度为 (O(n))

  • 相关阅读:
    Jmeter+maven+Jenkins构建云性能测试平台(mark 推荐)
    Jenkins配置自动化构建
    跟我学Shiro---无状态 Web 应用集成
    Tomcat下部署多个项目
    Guava 教程1-使用 Google Collections,Guava,static imports 编写漂亮代码
    Spring-security-Oauth2.0
    OAuth2
    从产品角度看,怎样的知识付费才是用户要的?
    REST API 基于ACCESS TOKEN 的权限解决方案
    一个想法照进现实-《IT连》创业项目:关于团队组建
  • 原文地址:https://www.cnblogs.com/EdisonBa/p/14953367.html
Copyright © 2020-2023  润新知