KMP算法(Java)

        KMP(Knuth-Morris-Pratt)算法是一种用于字符串搜索的高效算法,典型的应用场景是在一个文本字符串中查找一个模式(或子字符串)的出现位置。它的效率之所以高,是因为它能够在不回溯文本字符串指针的情况下,通过预处理模式字符串来有效地跳过字符。

KMP算法的核心思想

        KMP算法的核心在于预处理模式字符串,构建一个部分匹配表(也称为KMP表),该表用于在不匹配时决定文本指针的下一步位置。具体而言,部分匹配表记录了模式字符串的每个位置之前(不包括该位置字符)的最长相等前后缀的长度。利用这个表,算法能够在发生不匹配时,将模式字符串向右移动至适当的位置继续匹配,而无需重新检查之前已匹配的字符。

部分匹配表的计算

假设我们有一个模式字符串"ABCDABD",我们现在计算其部分匹配表(以0开始):

  • "A"的前后缀都为空集,最长公共元素长度为0
  • "AB"的前后缀为空集,最长公共元素长度为0
  • "ABC"的前后缀为空集,最长公共元素长度为0
  • "ABCD"的前后缀为空集,最长公共元素长度为0
  • "ABCDA"的前缀为{"A", "AB", "ABC", "ABCD"},后缀为{"BCDA", "CDA", "DA", "A"},最长公共元素为"A",长度为1
  • "ABCDAB"的前缀为{"A", "AB", "ABC", "ABCD", "ABCDA"},后缀为{"BCDAB", "CDAB", "DAB", "AB", "B"},最长公共元素为"AB",长度为2
  • "ABCDABD"的前后缀为空集,最长公共元素长度为0

因此,模式字符串"ABCDABD"的部分匹配表为lps[0, 0, 0, 0, 1, 2, 0]

搜索过程

        KMP算法利用部分匹配表进行搜索时,当文本字符串中的字符与模式字符串中的字符不匹配时,可以根据模式字符串的部分匹配表值跳过一些字符匹配尝试。这样,算法就不需要从文本字符串的下一个字符重新开始匹配模式字符串,从而提高搜索效率。

        以模式字符串 "ABCDABD" 和一个假设的文本字符串 "ABC ABCDAB ABCDABCDABDE" 为例:
匹配过程:
        文本 (txt): "ABC ABCDAB ABCDABCDABDE"  模式 (pat): "ABCDABD"  文本索引 (i): 0 模式索引 (j): 0。


第一轮匹配:
txt[0] = 'A', pat[0] = 'A' -> 匹配成功, i 和 j 都加 1
txt[1] = 'B', pat[1] = 'B' -> 匹配成功, i 和 j 都加 1
txt[2] = 'C', pat[2] = 'C' -> 匹配成功, i 和 j 都加 1
txt[3] = '  ',  pat[3] = 'D' -> 匹配失败, j 不为 0, 所以 j 跳转到 lps[j-1] = lps[2] = 0


第二轮匹配(从文本索引4开始,因为 i 不回溯):
txt[4] = 'A', pat[0] = 'A' -> 匹配成功, i 和 j 都加 1
以此类推,直到 pat[4] = 'A', txt[8] = 'A' 时匹配成功,j 加 1 到 5
txt[9] = 'B', pat[5] = 'B' -> 匹配成功, j 加 1 到 6
txt[10] = ' ', pat[6] = 'D' -> 匹配失败, j 跳转到 lps[j-1] = lps[5] = 2
第三轮匹配:(文本索引为11,模式索引跳到2):
txt[11] = 'A', pat[2] = 'C' -> 匹配失败, j 跳转到 lps[j-1] = lps[1] = 0


后续匹配:
        继续以上步骤,直到发现整个模式字符串在文本字符串中的匹配位置。在这个例子中,完整的匹配将在文本字符串的索引15开始。KMP算法通过部分匹配表(lps)避免了从文本字符串的每一个新位置重新开始搜索模式字符串,这显著提高了搜索效率。当在某一位置发现不匹配时,从部分匹配表中可以得出下一个可能匹配的模式字符串的位置,从而避免了不必要的比较。

算法的优势

        KMP算法的主要优势在于其搜索效率。在最坏的情况下,KMP算法的时间复杂度是O(n),其中n是文本字符串的长度。这比最简单的字符串搜索算法(如朴素字符串搜索算法,其时间复杂度为O(n*m),其中m是模式字符串的长度)要高效得多。

例题1:

P3375 【模板】KMP - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)


import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String s1 = sc.next();
        String s2 = sc.next();

        int[] lps = computeLPSArray(s2);
        search(s1, s2, lps);

        // 打印每个长度的前缀的最长 border 长度
        for (int i = 0; i < lps.length; i++) {
            System.out.print(lps[i] + " ");
        }
    }

    // KMP搜索函数
    public static void search(String txt, String pat, int[] lps) {
        int M = pat.length();
        int N = txt.length();
        int i = 0; // txt的索引
        int j = 0; // pat的索引
        while (i < N) {
            if (pat.charAt(j) == txt.charAt(i)) {
                j++;
                i++;
            }
            if (j == M) {
                System.out.println(i - j + 1);
                j = lps[j - 1];
            }
            else if (i < N && pat.charAt(j) != txt.charAt(i)) {
                if (j != 0) {
                    j = lps[j - 1];
                } else {
                    i = i + 1;
                }
            }
        }
    }

    // 计算LPS(Longest Prefix Suffix)数组
    public static int[] computeLPSArray(String pat) {
        int len = 0;
        int i = 1;
        int M = pat.length();
        int[] lps = new int[M];
        lps[0] = 0; // lps[0]总是0

        while (i < M) {
            if (pat.charAt(i) == pat.charAt(len)) {
                len++;
                lps[i] = len;
                i++;
            } else {
                if (len != 0) {
                    len = lps[len - 1];
                } else {
                    lps[i] = len;
                    i++;
                }
            }
        }
        return lps;
    }
}

例题2:

Many Equal Substrings - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int k = sc.nextInt();
        sc.nextLine();
        String str = " " + sc.nextLine();
        char[] a = str.toCharArray();
        int[] kmp = new int[51];

        // 计算KMP表
        int i = 2, j = 0;
        while (i <= n) {
            while (j > 0 && a[i] != a[j + 1]) j = kmp[j];
            if (a[i] == a[j + 1]) kmp[i++] = ++j;
            else kmp[i++] = j;
        }
        for(i = 1; i <= n; i++) {
            System.out.print(a[i]);
        }

        for(i = 2; i <= k; i++) {
            for(j = kmp[n] + 1; j <= n; j++) {
                System.out.print(a[j]);
            }
        }
   

例题3: 

kmp算法_牛客题霸_牛客网 (nowcoder.com)

import java.util.*;

public class Solution {
    // KMP字符串匹配算法
    public int kmp (String S, String T) {
        // 获取字符串和模式串的长度
        int m=S.length(),n=T.length();
        // 特殊情况处理
        if(m>n||n==0) return 0;
        // 初始化匹配次数计数器,准备next数组
        int cnt=0;
        int[] next=getNext(S);
        // 遍历主字符串和模式串进行匹配
        for(int i=0,j=0;i<n;i++){
            // 当字符不匹配时,利用next数组找到下一位置
            while(j>0&&T.charAt(i)!=S.charAt(j)){
                j=next[j-1];
            }
            // 当前字符匹配成功,移动模式串索引
            if(T.charAt(i)==S.charAt(j)) j++;
            // 完整匹配一次模式串
            if(j==m){
                // 匹配次数增加,模式串索引按next数组回退
                cnt++;
                j=next[j-1];
            }
        }
        return cnt;
    }
    // 构建next数组
    private int[] getNext(String S){
        int m=S.length();
        int[] next=new int[m];
        for(int i=1,j=0;i<m;i++){
            // 当前后缀字符不匹配时,回退
            while(j>0&&S.charAt(i)!=S.charAt(j)){
                j=next[j-1];
            }
            // 当前后缀字符匹配,继续
            if(S.charAt(i)==S.charAt(j)) j++;
            // 设置next值
            next[i]=j;
        }
        return next;
    }

}

例题4: 

5.小明的字符串 - 蓝桥云课 (lanqiao.cn)

import java.util.Scanner;

public class Main {
    public static int ans;
    public static int[] next(char [] s) {
        int[] next = new int[s.length];
        next[0]=0;
        int j=0;
        for(int i=1;i<next.length;i++) {
            while (j>0 && s[i]!=s[j]) j=next[j-1];
            if(s[i]==s[j]) j++;
            next[i]=j;
        }
        return next;

    }

    public static int  kmp(char[] S,char[] T) {

        int next[]= next(T);
        int j=0;
        for(int i=1;i<S.length;i++) {
            while (j>0 && S[i]!=T[j]) j=next[j-1];
            if (S[i]==T[j]) {
                j++;
                ans = Math.max(ans, j);
            }
            if(j>T.length-1) {
                break;
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String S=scanner.nextLine();
        String T = scanner.nextLine();
        char []a=S.toCharArray();
        char []b=T.toCharArray();
        System.out.println(kmp(a,b));
    }
}

例题5: 

10.boarder - 蓝桥云课 (lanqiao.cn)

import java.util.Scanner;

public class StringCycle {
    private static int[] next(String s) {
        int len = s.length();
        int[] next = new int[len];
        next[0] = 0;
        for (int i = 1, j = 0; i < len; i++) {
            while (j != 0 && s.charAt(i) != s.charAt(j)) {
                j = next[j - 1];
            }
            if (s.charAt(i) == s.charAt(j)) {
                j++;
            }
            next[i] = j;
        }
        return next;
    }
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String s = sc.next();
        int[] next = next(s);
        int len = s.length();
        int cir = len - next[len - 1];
        if (len % cir == 0) {
            System.out.println(len / cir);
        } else {
            System.out.println(1);
        }
    }
}

  • 34
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
KMP算法(Knuth-Morris-Pratt算法)是一种字符串匹配算法,用于在一个主串中查找一个模式串的出现位置。它的核心思想是利用已经匹配过的部分信息,尽量减少不必要的比较次数,从而提高匹配效率。 KMP算法的实现主要包括两个步骤:构建next数组和进行匹配。 1. 构建next数组: - 首先,我们需要定义一个next数组,用于存储模式串中每个位置的最长公共前后缀长度。 - 然后,从模式串的第二个字符开始,依次计算每个位置的最长公共前后缀长度。 - 最后,将计算得到的最长公共前后缀长度填入next数组中。 2. 进行匹配: - 在匹配过程中,我们需要维护两个指针:主串指针i和模式串指针j。 - 当主串和模式串的当前字符匹配时,i和j同时向后移动一位。 - 当主串和模式串的当前字符不匹配时,根据next数组的值调整模式串指针j的位置。 - 如果j等于模式串的长度,表示匹配成功,返回主串中匹配的起始位置。 下面是KMP算法Java实现示例: ```java public class KMP { public static int kmp(String text, String pattern) { int[] next = getNext(pattern); int i = 0, j = 0; while (i < text.length() && j < pattern.length()) { if (j == -1 || text.charAt(i) == pattern.charAt(j)) { i++; j++; } else { j = next[j]; } } if (j == pattern.length()) { return i - j; } else { return -1; } } private static int[] getNext(String pattern) { int[] next = new int[pattern.length()]; next[0] = -1; int i = 0, j = -1; while (i < pattern.length() - 1) { if (j == -1 || pattern.charAt(i) == pattern.charAt(j)) { i++; j++; next[i] = j; } else { j = next[j]; } } return next; } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值