数据结构 四、字符串 和 KMP算法

串:内容受限的线性表

(数据元素只能是字符)

串:String--- 字符组成的有限序列
顺序储存用的多
案例:病毒感染检测(病毒dna环状)

结构类型定义

#define MAXLEN 255
typedef struct
{
     char ch[MAXLEN+1];
     int length;
}SString;
  • 下面是链串定义
#define CHUNKIZE 80//块大小
typedef struct Chunk //块
{
    char ch[CHUNKIZE];
    struct Chunk *next;
}Chunk;
typedef struct
{
    Chunk *head,*tail;//头指针,尾指针
    int curlen;//串的当前长度
}LString;  //字符串的块链结

模式匹配算法

提示:
【如果某串是循环串,将该串连续储存两次,然后再匹配】
算法作用:
确定主串(正文串)所含字串(模式串)第一次出现的位置(定义)

分类:

  1. BF算法(暴力)( O(n*m) )
  2. KMP算法(O(n))

BF算法(暴力)

时间复杂度:( O(n*m) )
简述:

  • 回溯:i={ i - ( j-1 ) } + 1=i-j+1+1
  • 重头开始:j=1;
  • 字串出现位置:POS=i-t.length

代码:
就不解释了,一看就懂

int index_BF(String S,String T,int pos)
//S表示主串(被匹配串),T表示匹配串
//pos表示从主串第pos个位置开始匹配
{
    int i=pos;//i指向主串
    int j=0;//j指向匹配串
    while(i<S.length&&j<T.length)
    {
        if(S.ch[i]==T.ch[j])
        {
            i++;
            j++;
        }
        else
        {
            i=i-j+1;
            j=0;
        }
    }
    if(j>=T.length)
    {
        return i-T.length;
    }
    else return -1;
}

KMP算法

概述

优点: 速度快

  • 主串 不必回溯:
  • 模式串 不必重头开始:j=1;

难点:获取next[j]

  • next[j]含义1:当第 j 个字符与主串中相应的字符“匹配失败”时,模式串 重新与主链匹配 的位置
  • 含义2:next[j] = k 代表T[j] 之前的子串中,有长度为k 的相同前缀和后缀。

算法实现

前期准备:
列出所有公共子序列,求最长公共前后缀
image
得到前缀表
image

KMP过程:

流程:

  • 假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置
  • 如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++,继续匹配下一个字符;
  • 如果j != -1,且当前字符匹配失败,则令 i 不变,j = next[j]。此举意味着失配时,模式串P相对于文本串S向右移动了j - next [j] 位。

image

(匹配正确,匹配字串下一个)

image

错误匹配

image

看前缀表,从前缀表写的位置开始匹配

image

代码实现

获取next[j]

含义:
next[j] = k 代表T[j] 之前的子串中,有长度为k 的相同前缀和后缀。
思想:
只有 较长字串比较短字串多的那个字符,是较短字符的最长公共前后缀的前缀的下一个字符, 才有 较长字串的最长公共前后缀的长度是较短的长度+1;
否则就是和找上一个,直到找到匹配的

便于理解代码1(使用结构体):

void get_next(String T,int next[])
{
    next[0]=0;
    /*
      作用:初始化
      含义:当串T第一个元素匹配失败后,
        下一次匹配串 T还是从第1格元素开始匹配
    */
    int len=0;
    /*
      len含义:
        前一个的最长公共子序列长度
        即:最后一个元素所在位置
    */
    int i=1;
    /*

      i含义:
      . 正在检测串的第i个字母
    */
      while(i<T.length)
    {
        if(T.ch[i]==T.ch[len])//
        {
            len++;
            next[i]=len;
            i++;
        }
        else
        {
            if(len>0)
            {
          /*aabcd ae aabce 此时len指向d,i指向e
            然后去找前一个最长公共序列长度.*/
                len=next[len-1];
            }
            else
            {
                next[i]=len;//即next[i]=0;
                i++;
            }
        }
    }
    //为了方便运算,向后移一位
    for(i=T.length; i>0; i--)
    {
        next[i]=next[i-1];
    }
    next[0]=-1;
}

下面来自链接

上面代码便于理解,但是最后需要整体向后移动一位,下面代码直接创建next数组时候便建立向后移动一位的代码

便于理解代码2:

char S[100];
char T[100];
int slen;
int tlen;
void get_next(int next[])
{
    next[0]=-1;
    int alpha=-1;
    int j=0;
    while(j<tlen)
    {
        if(alpha==-1||T[alpha]==T[j])
        {
            alpha++;
            j++;
            next[j]==(alpha+1)-1;
			//+1代表让第k个字符,所处位置是k(而不是k-1)
			//-1代表处理的是前一个串
        }
        else
        {
            alpha=next[alpha];
        }
    }
}

解释1:为什么 alpha=next[alpha];
根据对称性,只需再判断p[next[k]]与p[j]是否相等即可,于是令k = next[k]

上面代码已经完善,但是还有个小问题,匹配串有重复时,可以再优化

案例
此时匹配失败
image
跳转到该处
image
但是跳转到该处,必然失配。

原因:

  • 当p[j] != s[i] 时,下次匹配必然是p[ next [j]] 跟s[i]匹配,
  • 如果p[j] = p[ next[j] ],必然导致后一步匹配失败
    (因为p[j]已经跟s[i]失配,然后你还用跟p[j]等同的值p[next[j]]去跟s[i]匹配,很显然,必然失配)
  • 所以不能允许p[j] = p[ next[j ]]。

解决方案:
令next[j] = next[ next[j] ],即可解决,动手画画就可以理解

最终代码:

char S[100];
char T[100];
int slen;
int tlen;
void get_next(int next[])
{
    next[0]=-1;
    int alpha=-1;
    int j=0;
    while(j<tlen)
    {

        if(alpha==-1||T[alpha]==T[j])
        {
            alpha++;
            j++;
            if(T[j]!=T[alpha])
            {
                 next[j]==alpha;
            }
            else
            {
                   next[j]=next[alpha];
            }


        }
        else
        {
            alpha=next[alpha];
        }
    }
}

index_KMP

常用版本:

void index_KMP()
{
    int next[tlen];
    GetNext(next);
    int i=0;
    int j=0;
    while(i<slen)
    {
        if(j==tlen-1&&S[i]==T[j])
        {
             cout<<i-j+1<<endl;
            return;
        }
        if(j==-1||S[i]==T[j])
        {
            i++;
            j++;
        }
        else
        {
            j=next[j];
        }
    }
    cout<<"-1"<<endl;
}

使用结构体版:

void index_KMP(String S,String T,int pos)
{
    int i=pos;
    int j=0;
    int next[T.length];
    get_next(T,next);
    while(i<S.length)
    {
        if(j==T.length-1 && S.ch[i]==T.ch[j])
        {
            printf("%d\n",i-j+1);
            return;
            //j=next[j];
            /*如果不return,就会搜寻下一个符合的*/
        }
        if(j==-1||S.ch[i]==T.ch[j])
        {
            i++;
            j++;
        }
        else
        {
            j=next[j];
        }
    }
}


模板代码

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 10;
#define endl '\n'
#define ll long long
void init()
{
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    ios::sync_with_stdio(false);
}
char s[N];
char t[N];
int slen;
int tlen;
int nex[N];
void get_next()
{
    nex[0] = -1;
    int pre = -1;
    int j = 0;
    while (j < tlen)
    {
        if (pre == -1 || t[pre] == t[j])
        {
            pre++;
            j++;
            if (t[j] != t[pre])
                nex[j] = pre;
            else
                nex[j] = nex[pre];
        }
        else
            pre = nex[pre];
    }
}
void index_KMP()
{

    get_next();
    int i = 0;
    int j = 0;
    while (i < slen)
    {
        if (j == tlen - 1 && s[i] == t[j])
        {
            cout << i - j + 1 << endl;
            return;
        }
        if (j == -1 || s[i] == t[j])
        {
            i++;
            j++;
        }
        else
            j = nex[j];
    }
    cout << "-1" << endl;
}
signed main()
{
    init();
    while (cin >> s)
    {
        cin >> t;
        tlen = strlen(t);
        slen = strlen(s);
        index_KMP();
    }

    return 0;
}
posted @ 2021-08-28 08:51  kingwzun  阅读(125)  评论(0编辑  收藏  举报