第四章 串、数组和广义表 —— BF算法和KMP算法

BF算法和KMP算法


本节讲的是字符串匹配算法。

BF算法

在这里插入图片描述这个其实就是暴力匹配,若主串长度为 n n ,子串长度为 m m 的话此算法的时间复杂度为 O ( m n ) O(m*n)

代码如下:

#include<bits/stdc++.h>

#define OK 1
#define ERROR 0
#define MAXSTRLEN 255

typedef int Status;
typedef int ElemType;
typedef unsigned char SString[MAXSTRLEN + 1];  //0号单元存放串的长度
using namespace std;

Status StrAssign(SString T,char chars[])
{
    //赋值
    //生成一个其值等于chars的串T。
    int i;
    if(strlen(chars)>MAXSTRLEN)
        return ERROR;
    T[0]=strlen(chars);
    for(i=0; i<=T[0]; i++)
    {
        T[i+1]=chars[i];
    }
    return OK;
}

int BF(SString S,SString T,int pos)
{
    int i = pos;
    int j = 1;
    if (pos<1)
    {
        cout<<"输入的位置不合理"<<endl;
        return 0;
    }
    while (i<=S[0]&& j<=T[0])
    {
        if (S[i]==T[j])
        {
            i++;
            j++;
        }
        else
        {
            i = i-(j-1)+1;//(j-1)代表i徒劳的向前移动了(j-1)个位置
            j = 1;
        }
    }
    if (j>T[0]&&T[0])
        cout<<"子串在主串中首字母的位置为"<<i-T[0]<<endl;

    else
       cout<<"在主串第"<<pos<<"个位置后未查询到子串"<<endl;
    return 0;

}

int main()
{
    int pos;
    SString S,T;
    char a[100], b[100];
    cout << "请输入主串:" ;
    cin >> a;
    StrAssign(S, a);
    cout<<"请输入要查询的子串:";
    cin>>b;
    StrAssign(T, b);
    cout<<"请输入从子串的哪个位置之后开始查询:";
    cin>>pos;
    BF(S,T,pos);
    return 0;
}


KMP算法

KMP算法就是尽可能的把BF算法中的无意义的部分去掉。时间复杂度为 O ( m + n ) O(m+n) .

在这里插入图片描述

  • 主串的指针 i i 不回溯
  • 当在 P j P_j 处失配时,模式串的指针 j j 不回溯到1,而是根据已匹配的区间( P 1 P_1 —— P j 1 P_{j-1} )的前后缀重合部分尽可能的向右移动。上图回溯到 P k P_k 处。

KMP 算法的难点就在于去找到当主串在 S i S_i 处失配后应该跟模式串的哪一个位置再开始匹配。这便是Next函数去解决的问题了。

Next函数的思路:
1.定义next[1] = 0.这个是为了方便函数的实现定的一个规定,之后你就知道奥妙了。
2.当模式串在 j j 处跟主串失配后去看模式串已匹配区间[1, j-1]的最大相等前后缀的值,然后把这个值加1就是模式串在失配处的next值了。

eg:
在这里插入图片描述

  • j = 1 j = 1 时我们规定next[j] = 0
  • j = 4 j = 4 时我们看已匹配区间[1,3]的最大相等前后缀。这里“abc”的最大相等前后缀为0,加一之后为1.所以没有重合部分时我们只能老老实实的从第一个开始比较了。
  • j = 5 j = 5 时我们看已匹配区间[1,4]的最大相等前后缀。这里“abca”的最大相等前后缀为1(a),加一之后为2.所以当位置5失配时我们让主串的第5个位置跟模式串的第2个位置比较,这样正好主串的第4个位置跟模式串的第1个位置都是a。
  • j = 12 j = 12 时我们看已匹配区间[1,11]的最大相等前后缀。这里“abcaabbcabc”的最大相等前后缀为3(abc),加一之后为4.所以当位置12失配时我们让主串的第12个位置跟模式串的第4个位置比较,这样正好主串的第9,10,11个位置跟模式串的第1,2,3个位置都是abc。
    ……

Next函数就是为了把模式串的每一个位置失配后要移动到的下一个位置保存在next数组里。

void Next(SString T,int next[])
{
    int j = 1;
    int k = 0;
    next[1] = 0;
    while(j<T[0])
    {
        if(T[j]==T[k] || !k)
        {
            j++;
            k++;
            next[j] = k;
        }
        else
            k = next[k];
    }
}

假设模式串为“abab”我们来根据代码跑一边。
1.最开始k = 0.所以!k肯定成立。执行if语句:j = 2,k = 1,next[2] = 1.
2.继续while循环,此时if条件不成立,k = next[1] = 0.
3.继续while循环,此时!k成立。执行if语句:j = 3,k = 1,next[3] = 1.
4.继续while循环,此时T[3] == T[1]成立。执行if语句:j = 4,k = 2,next[4] = 2.

这个正好就是和咱们之前讲的next[ j ] 等于已匹配区间[ 1 , j-1 ]的最大相等前后缀的值加一。

到此如果还没看懂可以看下这个:漫画讲解KMP

完整代码如下:

#include<bits/stdc++.h>

#define OK 1
#define ERROR 0
#define MAXSTRLEN 255

typedef int Status;
typedef int ElemType;
typedef unsigned char SString[MAXSTRLEN + 1];  //0号单元存放串的长度
using namespace std;

Status StrAssign(SString T,char chars[])
{
    //赋值
    //生成一个其值等于chars的串T。
    int i;
    if(strlen(chars)>MAXSTRLEN)
        return ERROR;
    T[0]=strlen(chars);
    for(i=0; i<=T[0]; i++)
    {
        T[i+1]=chars[i];
    }
    return OK;
}

int KMP(SString S,SString T,int pos,int next[])
{
    int i = pos;
    int j = 1;
    if (pos<1)
    {
        cout<<"输入的位置不合理"<<endl;
        return ERROR;
    }
    while (i<=S[0]&&j<=T[0])
    {
        if (S[i]==T[j] || j==0)
        {
            i++;
            j++;
        }
        else
            j = next[j];//失配后回到一个适当的位置
    }
    if (j>T[0])
    {
        cout<<"子串在主串中首字母的位置为:"<<i-T[0];
        return OK;
    }
    else
    {
        cout<<"在主串第"<<pos<<"个位置后未查询到子串"<<endl;
        return OK;
    }

}

void Next(SString T,int next[])
{
    int j = 1;
    int k = 0;
    next[1] = 0;
    while(j<T[0])
    {
        if(T[j]==T[k] || !k)
        {
            j++;
            k++;
            next[j] = k;
        }
        else
            k = next[k];
    }
}

int main()
{
    int pos;
    SString S,T;
    char a[100], b[100];
    cout << "请输入主串:" ;
    cin >> a;
    StrAssign(S, a);
    cout<<"请输入要查询的子串:";
    cin>>b;
    StrAssign(T, b);
    cout<<"请输入从子串的哪个位置之后开始查询:";
    cin>>pos;
    int next[T[0]+1];
    Next(T,next);
    KMP(S,T,pos,next);
    return 0;
}


Next函数优化Nextval

假如在某处字符a失配,根据next算好的跳转位置后还是字符a,那显然还是失配的。Nextval就是来把这些无意义的步骤给省去的。

eg:
在这里插入图片描述

  • 在位置3处失配,由next数组得跳转到位置1.但是位置1处还是个a,失配后再跳转到位置0.如果用nextval数组我们就可以直接跳转到位置0处,省去跳转到位置1这一步。
  • 所以nextval的求法就是在next的基础上看跳转后的字符是否相等,如果相等那么就用之前的nextval值来当这个的nextval。如果不相等那就用它本身的next当nextval。

这个实现起来也很简单,加一个if判断就ok了。代码如下:

#include<bits/stdc++.h>

#define OK 1
#define ERROR 0
#define MAXSTRLEN 255

typedef int Status;
typedef int ElemType;
typedef unsigned char SString[MAXSTRLEN + 1];  //0号单元存放串的长度
using namespace std;

Status StrAssign(SString T,char chars[])
{
    //赋值
    //生成一个其值等于chars的串T。
    int i;
    if(strlen(chars)>MAXSTRLEN)
        return ERROR;
    T[0]=strlen(chars);
    for(i=0; i<=T[0]; i++)
    {
        T[i+1]=chars[i];
    }
    return OK;
}

int KMP(SString S,SString T,int pos,int nextval[])
{
    int i = pos;
    int j = 1;
    if (pos<1)
    {
        cout<<"输入的位置不合理"<<endl;
        return ERROR;
    }
    while (i<=S[0]&&j<=T[0])
    {
        if (S[i]==T[j] || j==0)
        {
            i++;
            j++;
        }
        else
            j = nextval[j];//失配后回到一个适当的位置
    }
    if (j>T[0])
    {
        cout<<"子串在主串中首字母的位置为:"<<i-T[0];
        return OK;
    }
    else
    {
        cout<<"在主串第"<<pos<<"个位置后未查询到子串"<<endl;
        return OK;
    }

}

void Nextval(SString T,int nextval[])
{
    int j = 1;
    int k = 0;
    nextval[1] = 0;
    while(j<T[0])
    {
        if(T[j]==T[k] || !k)
        {
            j++;
            k++;
            if (T[j]!=T[k])
                nextval[j] = k;
            else
                nextval[j] = nextval[k];
        }
        else
            k = nextval[k];
    }
}

int main()
{
    int pos;
    SString S,T;
    char a[100], b[100];
    cout << "请输入主串:" ;
    cin >> a;
    StrAssign(S, a);
    cout<<"请输入要查询的子串:";
    cin>>b;
    StrAssign(T, b);
    cout<<"请输入从子串的哪个位置之后开始查询:";
    cin>>pos;
    int nextval[T[0]+1];
    Nextval(T,nextval);
    KMP(S,T,pos,nextval);
    return 0;
}


原创文章 85 获赞 46 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Deam_swan_goose/article/details/105348760