BF简单匹配算法和KMP匹配算法

BF和KMP匹配算法

  • 一、BF匹配算法
  • 二、KMP匹配算法

一、BF匹配算法

BF模式匹配算法,又称朴素模式匹配算法,简单模式匹配算法,暴力匹配算法。对于字符串,现有一个主串和一个子串,那么子串在主串中的定位操作通常称为串的模式匹配。下面我们来了解一下BF匹配算法。
假设有一主串S = “goodgoogle”,子串T = “google”,那么找到子串在主串中的位置,我们直接想到的方法就是如下:

  • 1、先将子串从主串的头部开始比较。其中直线符号为相等,闪电符号为不相等。
    BF简单匹配算法和KMP匹配算法_第1张图片
  • 2、遇到不相等的之后,将子串向右滑动一个单位,继续重新开始比较。
    BF简单匹配算法和KMP匹配算法_第2张图片
  • 3、依次类推,直到在主串中匹配到子串为止。
    BF简单匹配算法和KMP匹配算法_第3张图片
    注:在C/C++、Java中字符串末尾以’/0‘结尾,所以当匹配到’/0‘还没有匹配上时,那就证明主串中没有子串。
    上述过程也就是我们的BF算法,因为其简单直观,我们即称其为简单算法或暴力算法。下面我们来分析一下BF算法的具体过程:

我们设主串S和子串T的下标都从0开始,i=0;j=0。
BF简单匹配算法和KMP匹配算法_第4张图片
当T[j]=S[i]时,i和j均加一,也就是均往右移动一个单位,继续进行比较。
BF简单匹配算法和KMP匹配算法_第5张图片
当比较到i=j=3时,T[j] != S[i]。那么子串T中j就要回溯到子串的开头,也就是j=0的位置,而主串S中的i需要回溯到比较的下一位,也就是i=i-j+1。
BF简单匹配算法和KMP匹配算法_第6张图片
BF简单匹配算法和KMP匹配算法_第7张图片
如果匹配成功后,返回匹配成功处串的第一个位置,如果不成功就是返回-1.
以此类推,代码如下:

int BF(char S[],char T[])  //BF算法--模式匹配
{
    int i = 0;
    int j = 0;
    while (S[i] != '\0' && T[j] != '\0')  //只要两个串不到串的末尾就一直循环
    {
        if(S[i] == T[j]){
            i++;
            j++;
        }
        else{
            i = i-j+1;   //i回溯
            j = 0;      //j回溯
        }
    }
    if (S[i] == '\0'){
        return -1;
    }
    else{
        return i-j;
    }
}

那么我们可以看到,在BF算法中,子串‘google’和主串‘goodgoogle’前三个字符是相同的,在i=j=3的时候匹配失败,需要回溯,因为前三个字符相同,并且子串中‘g’不等于后面的’o‘,所以此时子串中的’g‘一定不等于主串中的’o‘,但是BF算法照样进行了一次比较,这就造成了效率的下降。所以我们应该对其进行优化,就有了下面的KMP算法。同时,理解KMP算法最好从BF算法的缺陷中入手,通过何种方式进行优化。

二、KMP匹配算法

KMP算法就是为了解决BF算法的低效问题。
上面我们说过子串‘google’和主串‘goodgoogle’前三个字符是相同的,在i=j=3的时候匹配失败,需要回溯。BF简单匹配算法和KMP匹配算法_第8张图片
BF简单匹配算法和KMP匹配算法_第9张图片
但是我们知道,在子串’google‘中,’g‘不等于其后面的两个’0‘,所以上图两个比较判断其实是多余的,这也是为什么BF算法低效的原因,也是BF算法的缺陷。
我们可以直接省略上图两步,将’g‘与’d‘进行比较。
BF简单匹配算法和KMP匹配算法_第10张图片
我们再讨论另一种情况:
假设S = ’abcdefgab‘,T = ’abcdex‘。我们第一次比较前五位主串字符和子串字符相等。
BF简单匹配算法和KMP匹配算法_第11张图片

因为’a‘不等于’b’,‘c’,‘d’,‘e’,所以后面的四步可以省略。
BF简单匹配算法和KMP匹配算法_第12张图片

有人可能会问,为什么我们要在i=5处重新进行比较呢,我们不是已经知道了’a’不等于’x’吗?首先T[5] != S[5],再者,我们虽然知道T[0] != T[5],但是我们却不知道T[0]和S[5]是否相等,所以要在i=5处比较。注意此处不同于上面讲的’goodgoodle‘,’google‘。在匹配失败处,子串中T[0] = T[3],而在
S = ’abcdefgab‘,T = ’abcdex‘中,匹配失败处,T[0] != T[5]。

由上面分析我们可以发现,主串i不进行回溯,只有子串中的j进行回溯。那么我们就要知道j应该回溯到那个位置呢?
我们需要对子串进行分析:
下面有一个数学描述:
BF简单匹配算法和KMP匹配算法_第13张图片
如果想要知道证明过程,请参考[ 数据结构(C语言版)严蔚敏 吴伟民 清华大学出版社]

下图是本人查阅参考书籍进行的一些总结,若想弄清楚数学上的证明过程,则可看下图,希望对读者有一点启发。若不想,即可跳过下图,并直接学习如何根据定义来求next数组。
BF简单匹配算法和KMP匹配算法_第14张图片

由上述next[j]定义可以求出子串的next数组的值。
BF简单匹配算法和KMP匹配算法_第15张图片
BF简单匹配算法和KMP匹配算法_第16张图片
那么对于T=‘abcabx’
BF简单匹配算法和KMP匹配算法_第17张图片
求解next数组代码:

void getNext(char *T, int *next)  //获得next数组的值
{
	int i,j;
    j = 0;
    i = 1;
    next[1] = 0;
    while(i < T.length){
        if(j == 0 || T[i] == T[j]){    //只针对子串T的比较
            i++;
            j++;
            next[i] = j;
        }
        else{
            j = next[j];     
        }
    }
}

求解next数组的代码其实就是上述数学表述(证明)的代码实现
在我们知道next数组如何求后,KMP算法代码如下:

int KMP(char S[],char T[],int pos)  //KMP算法
{
	int i,j;
    i = pos;   /*i为主串S当前元素下标值若i不为1,则从pos值开始*/
    j = 1;    //j为子串T当前元素下标值
    //若i=j=1.则在主串和子串的第一个元素比较,也即串的元素下标均从1开始
    int next[255];
    getNext(T,next);   //获取next数组
    lenS = strlen(S);
    lenT = strlen(T);
    while(i < lenS && j < lenT){   
        if(S[i] == T[i] || j = 0){ 
            i++;
            j++;
        }
        else{
            j = next[j]
        }
    }
    if(S[i] == '\0'){
        return -1
    }
    else
        return i-lenT
}

其中j=0的情况就是子串T中的第一个元素和主串S中对应比较的元素不相等。
并且next数组的第一个元素均为0,注意是next[1]=0。

参考书目:
数据结构 (C语言版) 严蔚敏 吴伟民 清华大学出版社
大话数据结构 程杰 清华大学出版社

你可能感兴趣的:(数据结构系列,数据结构与算法,算法,数据结构,模式匹配算法,字符串,KMP算法)