串及其模式匹配算法(KMP)

1,067 阅读3分钟

这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

1.串

由0个或多个字符组成的序列

2.存储结构(C语言)

  • 定长顺序存储(顺序存储结构)但串长是固定的
#define MAXSIZE 100 //定义长度为100
typedef char {
  char ch[MAXSIZE];
  int length;
} SString;
  • 堆分配存储(顺序存储结构)但串长是动态变化的
typedef struct {
  char * ch; //ch指向串的基地址
  int length; 
} SString;

//当我们使用堆分配存储时 我们需要动态的分配内存空间char*)malloc(i*sizeof(char))  //分配串空间
  • 块链存储表 这样存储密度高
#define SIZE 4
typedef struct StringNode{
 char ch[SIZE];  //每一个结点存多个字符 
 struct StringNode *next;
}StringNode,*Node;

3.KMP算法(模式匹配算法)

  • 基础知识 子串:串中任意个连续的字符组成的子序列

主串:包含字串的串

前缀集:除去最后一个字符后,一个字符串的全部头部组合 例如:aba的前缀集:{a,ab};

后缀集:指除了第一个字符以外,一个字符串的全部尾部组合 例如:aba的后缀集:{a,ba};

//求模式串的next数组
void getNext(SString T,int next[]){
  int i=1,j=0;
  next[1] = 0;
  while(i < T.length){
    if(j==0 || T.ch[i] == T.ch[j]) {
     i++;
     j++;
     next[i] = j;
  }else{
    j=next[j];
  }
}

//KMP算法 其中S时主串  T是模式串
int KMP(SSTring S,SString T) {
  int i=1,j=1;
  int next[T.length+1];
  getNext(T,next); //获取模式串的next数组
  while(i<=S.length && j<=T.length){
    if(j=0 || S.ch[i] == T.ch[j]){
      i++;
      j++;
    }else {
      j=next[j];
    }
  }
  if(j>T.length) {
    return i-T.length;
  }else {
     return 0;
  }
}

而在KMP算法中,与传统的模式匹配算法相比,传统的模式匹配算法是在主串有一个指针i 在子串有一个指针j 若S.ch[i]≠T.ch[j] 两个指针就要都要回溯 而KMP算法的优化就在 只让j指针回溯 i指针不动。那么问题就来了,j指针回溯到那个位置呢?这就是我们要求next数组的意义了 所以KMP算法的关键就要理解next数组是如何求出的 从上面getNext这个方法直接去看代码是很难一下就看懂的,我结合我看的视频来分享一下我的理解:

  • 首先我们规定next[1] = 0;
  • 在最理想的情况下:next[j+1] = next[j]+1; 但如果在不理想的情况下 Pk ≠ Pj 那么next[j+1]他可能第二个最大值是next[next[j]]+1 依次类推 这其中就有一个思想(递推)
  • 流程: 1)求next[j+1] (求j=5 即next[6])

2)已知next[j]=k,则有P1…Pk-1 = Pj-k+1…Pj-1 (next[5]=k=2,P[1] = P[4])

3)如果Pk=Pj,则P1…Pk-1PK = Pj-k+1…Pj-1Pj,则next[j+1]=k+1,否则 (p[2] = p[5] 则next[6]=next[5]+1-3)

4)已知next[k]=k2,则有P1…Pk2-1 = Pk-k2+1…Pk-1

5)第二第三步联合得到:P1…Pk2-1 = Pk-k2+1…Pk-1 = Pj-k+1…Pk2-k+j-1 = Pj-k2+1…Pj-1 即四段重合

6)Pk2=Pj,则P1…Pk2-1P~k2 = Pj-k2+1…Pj-1Pj,则next[j+1]=k2+1;

j12345678
Tabaabcac
next[j]01122312

在上面的基础之上再去理解这段代码 就很好理解了