【算法与数据结构】字符串模式匹配 KMP 算法

语义

在一个很长的字符串 T 中,查找是否存在子字符串 P。例如搜索引擎收录的大量网站数据,当用户输入关键字后,就会在这些数据中进行匹配,并返回合适的网站。

语义:假定字符串长度为 j,则所有字符串都在 [0, j) 这样的集合中。

返回首次匹配的字符的位置。注意这里调用方需要判断位置是否正确,例如对于长度为 i 的字符串,要查找是否有长度为 j 的字符串,如果返回值在 [0, i - j) 则为正确匹配到数据,否则就是失败。

暴力匹配 brute force

每次用 T 的一个字符匹配 pattern 的所有字符,全部匹配成功则返回首字符下标,否则 T 前进一个字符,继续匹配:

#include <stdio.h>
#include <string.h>

int match(char *str, char *pattern) {
    int i = 0, j = 0;
    int sLen = strlen(str);
    int pLen = strlen(pattern);
    if (sLen < 1 || pLen < 1) {
        return -1;
    }
    while (i < sLen && j < pLen) {
        if (str[i] == pattern[j]) {
            i++;
            j++;
        } else {
            i = i - j + 1;
            j = 0;
        }
    }
    return i - j;
}

int main(void) {
    char *str = "sfeiwojdsljfldshgew";
    printf("str length is:%ld\n", strlen(str));
    char *pattern[] = {
        "",
        "a",
        "abc",
        "woj",
        "wojdsljflda"
    };
    int i, ret;
    int len = sizeof(pattern) / sizeof(char *);
    for (i = 0; i < len; i++) {
        ret = match(str, pattern[i]);
        printf("ret is:%4d, correct range is:%ld, raw is:%s\n", ret, strlen(str) - strlen(pattern[i]), pattern[i]);
    }

    return 0;
}

KMP

最长公共前后缀

这里通常取的是真前缀(不包含最后一个字符)和真后缀(不包含第一个字符)。长度相同的前后缀就是公共前后缀。

例如:

  • a: ``,最长公共前后缀长度为 0
  • aa: a,最长公共前后缀长度为 1
  • ab: ``,最长公共前后缀长度为 0
  • aaa: aa,最长公共前后缀长度为 2
  • abab: ab,最长公共前后缀长度为 2

前缀表

任意的字符串 P,对其每个字符前面的子字符串找最长公共前后缀,得到的这个数组就是前缀表。
例如,对于字符串 ababc

  • a: ``,真前缀为空字符串,其最长公共前后缀长度记为 -1
  • ab: ``,真前缀为 a,其最长公共前后缀长度为 0
  • aba: a,真前缀为 ab,其最长公共前后缀长度为 0(最长前缀 a 和最长后缀 a 不匹配)
  • abab: ab,真前缀为 aba,其最长公共前后缀长度为 1
  • ababc: ``,真前缀为 abab,其最长公共前后缀长度为 2

最终的前缀表数组:[-1, 0, 0, 1, 2]

KMP 算法

package main

import "fmt"

func kmp(target string, pattern string) []int {
    var ret []int
    length := len(pattern)
    prefixTable := buildPrefixTable(pattern)
    i := 0
    k := 0
    for {
        if i + length > len(target) {
            break
        }
        if target[i + k] == pattern[k] {
            k += 1
            if k == length {
                ret = append(ret, i)
                k = 0
                i += 1
                continue
            }
        } else {
            diff := k - prefixTable[k]
            i += diff
            if k > 0 {
                k -= diff
            }
        }
    }
    return ret
}

func buildPrefixTable(str string) []int {
    ret := []int{-1}
    for i := range str {
        if i == 0 {
            continue
        }
        pre := str[:i]
        j := len(pre) - 1
        for j > 0 {
            if pre[:j] == pre[len(pre) - j:] {
                ret = append(ret, j)
                break
            }
            j = j - 1
        }
        if len(ret) < len(pre) + 1 {
            ret = append(ret, 0)
        }
    }
    return ret
}

func main() {
    ret := kmp("abacaca", "aca")
    fmt.Println(ret)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值