KMP(The Knuth-Morris-Pratt Algorithm)

WhaleFall541發表於2021-05-22

本文程式碼來自於中國大學MOOC

KMP課件下載

註釋內容為自己理解,如有錯誤請評論,或者私信給我,謝謝

圖1-1

圖1-1

match[j]的值實際上是前j個(包括j)元素的最大子串長度 對應到陣列中的位置 比如圖中 j = 6; 最大子串(abca)的長度為4,在陣列中的索引為3


圖1-2

圖1-2

當比較到後面不相等時,模式串相當於要後移到從上往下的第三個橫條的情形,也就是把第二個橫條情況p = match[p-1]+1


圖1-3

圖1-3
  • 第j個下標的字元和(match[j-1]+1)下標上的元素比較
  • 如果不匹配,則根據下標為match[j-1]的相同串基礎上進行條件比較
  • 因為match[j-1]已經存在,那麼綠紫色整塊和後面綠紫塊肯定一樣
  • 又第一個小綠塊為match[match[j-1]],綠塊和紫塊相同
  • 所以第一個綠塊和最後一個紫塊相同,只需比較問號位置的值即可
  • pattern[match[match[j-1]]+1]pattern[j] 的值是否相等

圖1-4

圖1-4
//此案例為C語言版本
#include <stdio.h>
#include "stdlib.h"
#include "string.h"

typedef int Position;

Position KMP(char string[25], char pattern[7]);

void BuildMatch(char *pattern, int *match);

#define NotFound -1

int main() {
    char string[] = "this is a simple example";
    char pattern[] = "simple";
    Position p = KMP(string, pattern);
    if (p == NotFound) printf("Not found.\n");
    else {
        printf("%s\n", string + p);
        printf("%d\n", p);
    }
    return 0;
}

Position KMP(char *string, char *pattern) {
    int n = strlen(string);
    int m = strlen(pattern);
    int s, p, *match;

    if (m > n) return NotFound;
    match = (int *) malloc(sizeof(int) * m);
    // 查詢match最長匹配字串位置值 例如:圖1-1
    // pattern a    b   c   a   b
    // index   0    1   2   3   4
    // match   -1   -1  -1  0   1
    BuildMatch(pattern, match);

    s = p = 0;
    while (s < n && p < m) {
        if (string[s] == pattern[p]) {
            s++;
            p++;
        } else if (p > 0) {
            // 將p置為 前p-1個元素 最大子串長度+1
            // 如圖1-2
            p = match[p - 1] + 1;
        } else
            s++;
    }
    return (p == m) ? (s - m) : NotFound;
}

void BuildMatch(char *pattern, int *match) {
    int i, j;
    int m = strlen(pattern);
    match[0] = -1;// -1 表示子串長度不存在,無任何相同的元素
    for (int j = 1; j < m; ++j) {
        // i表示前j-1個元素最大相同子串長度 陣列索引位置 index-length 0-1
        i = match[j - 1];

        while ((i >= 0) && (pattern[i + 1] != pattern[j]))
            // 第j個下標的字元和(match[j-1]+1)下標上的元素比較
            // 如果不匹配,則根據下標為match[j-1]的相同串基礎上進行條件比較
            // 因為match[j-1]已經存在,那麼綠紫色整塊和後面綠紫塊肯定一樣
            // 又第一個小綠塊為match[match[j-1]],綠塊和紫塊相同
            // 所以第一個綠塊和最後一個紫塊相同,只需比較問號位置的值即可
            // pattern[match[match[j-1]]+1] 和 pattern[j] 的值是否相等
            // 如圖 1-3
            i = match[i];

        if (pattern[i + 1] == pattern[j])
            // 如圖 1-4
            match[j] = i + 1;
            // 如果都匹配不上就直接設定為-1
        else match[j] = -1;
    }
}
// 此案例為Java版本 會輸出所有的匹配模式串的位置

/**
 * @Author: WhaleFall541
 * @Date: 2021/5/22 11:00
 */
public class KMP {
    public static void main(String[] args) {
        String s = "this is a simple example simple";
        String p = "simple";
        IndexKMP(s, p);
    }

    private static int IndexKMP(String sStr, String pStr) {
        char[] string = sStr.toCharArray();
        char[] pattern = pStr.toCharArray();
        if (sStr.length() < pStr.length()) return -1;
        int[] match = buildMatch(pattern);
        int s = 0, p = 0, n = 0;
        while (s < sStr.length()) {
            while (s < sStr.length() && p < pStr.length()) {
                if (string[s] == pattern[p]) {
                    s++;
                    p++;
                } else if (p > 0)
                    p = match[p - 1] + 1;
                else s++;
            }

            if (p == pStr.length()) {
                ++n;
                System.out.println("第" + n + "次匹配位置" + (s - pStr.length()) + "\n");
                p = 0;
            }
        }
        return 0;
    }

    private static int[] buildMatch(char[] pattern) {
        int[] match = new int[pattern.length];
        int i;
        match[0] = -1;
        for (int j = 1; j < pattern.length; j++) {
            i = match[j - 1];

            if (i >= 0 && pattern[i + 1] != pattern[j])
                i = match[i];

            if (pattern[i + 1] == pattern[j])
                match[j] = i + 1;

            else match[j] = -1;
        }

        return match;
    }
}


相關文章