KMP匹配算法

简介: KMP匹配算法

字串的定位操作通常称为串的的匹配模式

朴素的模式匹配算法

假设我们要匹配下面的主串S=“goodgoogle”,中,找到T="google"这个字串位置,我们按朴素模式匹配算法需要下面几步
1.从主串第一个位置开始,S与T的前三个都匹配成功,第四个匹配失败

S:g o o d g o o g l e
  | | | !
T:g o o g l e

2.从主串第二个位置开始,重新开始匹配

S:g o o d g o o g l e
    !  
T:  g o o g l e

3.继续上面过程

S:  g o o d g o o g l e
        !  
T:      g o o g l e

4.匹配失败,继续匹配

S:  g o o d g o o g l e
          !  
T:        g o o g g l e

5.匹配失败,继续匹配

S: g o o d g o o g l e
         !  
T:       g o o g l e

6.匹配成功。

S:g o o d g o o g l e
          | | | | | |
T:        g o o g l e

上面就是最普通的匹配算法
预备工作

#define Max 1000
typedef char String[Max];

代码如下
都是从下标为零的位置开始的

//朴素的模式匹配算法
int index(String S,String T,int pos)
{
    
    int i = pos-1;
    int j = 0;
    int len1 = strlen(S);
    int len2 = strlen(T);
    while (i < len1 && j <len2) 
    {
    
        if (S[i] == T[j])
        {
    
            ++j;
            ++i;
        }
        else
        {
    
            i = i - j + 1;     //匹配失败,回溯到首次匹配位置的下一位
            j = 0;
        }
    }
    if (j >= len2)           //若匹配成功,则返回匹配到的位置下标
        return i - len2;  
    else
        return 0;           //若匹配失败,则返回 0
}

KMP模式匹配算法

朴素的模式匹配算法的匹配算法虽然很容易理解,但效率极其低下,特别是当出现部分匹配的情况时,需要不断的回溯
所以聪明的前辈们想出了KMP模式匹配算法

我们先看看下面递推过程
S=“abcdeabcdef”,T=“abcdef”
进行匹配

S:a b c d e a b c d e f
  | | | | | !
T:a b c d e f

在匹配到 f 时匹配失败,按朴素模式匹配算法的话,下一步,因该又从第二位开始匹配
但我们知道
T(1) !=T(2) !=T(3) !=T(4) !=T(5) !=T(6)
并且
T(1)=S(1),T(2)=S(2),T(3)=S(3),T(4)=S(4)
可以推出,在S(6)之前是没有可以和T串匹配的字符串的,所以可以直接跳到S(6)位置开始匹配
如下:

S:a b c d e a b c d e f
            | | | | | |
T:          a b c d e f

匹配成功
再看下面例子:
S =“abcababca”,T=“abcabx”
先从第一个位置开始匹配

S:a b c a b a b c a b x
  | | | | | !    
T:a b c a b x

T(1)!=T(2)!=T(3)
因为
T(1)=S(1),T(2)=S(2),T(3)=S(3),T(4)=S(4),T(5)=S(5)

T(1)=T(4),T(2)=T(5)
所以我们只能保证,S(4)前的是没有和T串匹配的
所以这次匹配失败后
下一次可以直接跳到S(4)位置开始匹配
如下:

S:a b c a b a b c a b x
        | | !   
T:      a b c a b x

继续采用上面方法,下一次可以直接从S(6)位置开始匹配

S:a b c a b a b c a b x
            | | | | | |
T:          a b c a b x

匹配成功

通过上面方法进行字符串匹配的效率明显高于朴素模式匹配算法

next数组的推导

通过规律我们发现,匹配的关键是T串的相似度,在匹配前我们需要对T串进行研究
所以引出了next数组,来存放当前字符前的串的前后缀相似度

1.前缀: 包含首位字母但不包含末位字符的子串

2.后缀: 包含末位字符但不包含首位字符的子串

3.next数组的定义:当主串与模式串的某一位字符不匹配时,模式串要回退的位置

4.next [j]:其值 = 第j位字符前面 j-1位组成的子串的前后缀重合字符数 + 1

eg1:

j 1 2 3 4 5 6 7 8 9
模式串T a b a b a a a b a
next [ j ] 0 1 1 2 3 4 2 2 3

当 j=1 时,next[1]=0;
当 j=2 时, 前面就是有只有一个字符 a 所以,next[2] = 1;
当 j=3 时, 前面子串中没有重合前后缀 所以,next[3] = 1;
当 j=4 时,前面字串中 a 重合,所以next[4]=2;
当 j=5 时,前面字串中 ab 重合,所以next[5]=3;
当 j=6 时,前面字串中 aba 重合,所以next[6]=4;
当 j=7 时,前面字串中 a 重合,所以next[7]=2;
当 j=8 时,前面字串中 a 重合,所以next[8]=2;
当 j=9 时,前面字串中 ab 重合,所以next[9]=3;

eg2:

j 1 2 3 4 5 6 7 8 9
模式串T a a a a a a a a b
next [ j ] 0 1 2 3 4 5 6 7 8

当 j=1 时,next[1]=0;
当 j=2 时, 前面就是有只有一个字符 a 所以,next[2] = 1;
当 j=3 时, 前面字串中 a 重合,所以next[3]=2;
当 j=4 时,前面字串中 aa重合,所以next[4]=3;
当 j=5 时,前面字串中 aaa重合,所以next[5]=4;
当 j=6 时,前面字串中 aaaa重合,所以next[6]=5;
当 j=7 时,前面字串中 aaaaa重合,所以next[7]=6;
当 j=8 时,前面字串中 aaaaaa重合,所以next[8]=7;
当 j=9 时,前面字串中 aaaaaaa重合,所以next[9]=8;

代码:
都是从下标为零的位置开始的(上面推导过程是从一开始的)

void get_next(String T, int* next)
{
    
    int i, k;
    i = 1, k = 0;
    next[0] = 0;
    next[1] = 0;
    int len = strlen(T);
    while (i < len-1)
    {
    
        if (k == 0 || T[i] == T[k])
        {
    
            next[++i] = ++k;
        }
        else
        {
    
            k = next[k];
        }
    }
}

KMP算法代码

都是从下标为零的位置开始的

int index_KMP(String S, String T, int pos)
{
    
    int i = pos;
    int j = 0;
    int next[Max];
    get_next(T, next);
    int len1 = strlen(S);
    int len2 = strlen(T);
    while (i < len1 && j < len2)
    {
    
        if (j == 0 || S[i] == T[j])
        {
    
            ++i;
            ++j;
        }
        else
        {
    
            j = next[j];
        }
    }
    if (j >= len2)
        return i - len2;
    else
        return 0;
}

KMP模式匹配算法的改进

上面所讲的KMP算法只有在出现模式串与主串之间存在许多“部分匹配”的情况下,才能体现出他的优势
如下面
S =“aaabcde”,T =“aaaaax”
1.第一次匹配

S:a a a a b c d e q
  | | | | !
T:a a a a a x

2.第二次匹配

S:a a a a b c d e q
    | | | !
T:  a a a a a x

3.第三次匹配

S:a a a a b c d e q
      | | !
T:    a a a a a x

4.第四次匹配

S:a a a a b c d e q
        | !
T:      a a a a a x

5.第五次匹配

S:a a a a b c d e q
          !
T:        a a a a a x

匹配失败
由这个例子可看出,并没有的到什么优化
而模式串前面几个字符都是a,那么2,3,4,5那四次匹配显得非常多余,没有必要进行
我们直接可以拿首位的next [1]的值去代替与它相等的字符对应的后续next数组值

nextval数组的推导

j 1 2 3 4 5 6 7 8 9
模式串T a a a a a a a a b
next [ j ] 0 1 2 3 4 5 6 7 8
nextval [ j ] 0 0 0 0 0 0 0 0 8

代码:

void get_nextval(String T, int* nextval)
{
    
    int i, k;
    i = 1, k = 0;
    nextval[0] = 0;
    nextval[1] = 0;
    int len = strlen(T);
    while (i < len-1)
    {
    
        if (k == 0 || T[i] == T[k])
        {
    
            i++;
            k++;
            if (T[i] != T[k])
            {
    
                nextval[i]=k;
            }
            else
            {
    
                nextval[i] = nextval[k];
            }
        }
        else
        {
    
            k = nextval[k];
        }
    }
}
相关文章
|
6月前
|
算法
数据结构中的KMP算法及其改进算法
KMP算法通过引入部分匹配表,有效避免了重复计算,从而将字符串匹配的时间复杂度降低到O(m+n)。通过进一步优化next数组,KMP算法的效率得到了进一步提升。对于大规模字符串匹配问题,KMP算法及其改进算法提供了高效的解决方案,是计算机科学领域的经典算法之一。
93 3
|
2月前
|
算法
第四章 KMP算法理论基础
第四章 KMP算法理论基础
20 0
|
2月前
|
算法
KMP算法
KMP算法
35 0
|
4月前
|
算法 C++
A : DS串应用–KMP算法
这篇文章提供了KMP算法的C++实现,包括计算模式串的next数组和在主串中查找模式串位置的函数,用于演示KMP算法的基本应用。
|
5月前
|
数据采集 算法 JavaScript
揭开JavaScript字符串搜索的秘密:indexOf、includes与KMP算法
JavaScript字符串搜索涵盖`indexOf`、`includes`及KMP算法。`indexOf`返回子字符串位置,`includes`检查是否包含子字符串。KMP是高效的搜索算法,尤其适合长模式匹配。示例展示了如何在数据采集(如网页爬虫)中使用这些方法,结合代理IP进行安全搜索。代码示例中,搜索百度新闻结果并检测是否含有特定字符串。学习这些技术能提升编程效率和性能。
131 1
揭开JavaScript字符串搜索的秘密:indexOf、includes与KMP算法
|
4月前
|
算法
KMP算法
KMP算法
31 0
|
5月前
|
算法 Java
KMP算法详解及其在字符串匹配中的应用
KMP算法详解及其在字符串匹配中的应用
|
6月前
|
人工智能 算法 BI
一篇文章讲明白KMP算法(俗称看毛片算法)
一篇文章讲明白KMP算法(俗称看毛片算法)
60 0
|
6月前
|
算法 Java
Java数据结构与算法:字符串匹配算法之KMP算法
Java数据结构与算法:字符串匹配算法之KMP算法
|
7月前
|
算法