4.1 串的定义

串 (String)
由零个或多个字符组成的有限序列,一般记作 s="a₁a₂…aₙ"。其中 s 为串名,引号内为串值;n 为串的长度,n=0 时称为空串。
注意:空串 "" 长度为 0,与仅含一个空格的串不同。子串是串中任意连续字符组成的子序列,空串是任意串的子串。

串的基本操作

4.2 串的存储结构

1. 顺序存储

用数组存储串值。有两种方式:

2. 链式存储

每个结点存储一个或多个字符。结点大小为 1 时操作方便但指针开销大;结点大小大于 1 时可压缩存储,但操作更复杂。

4.3 串的模式匹配(BF 算法)

模式匹配
在主串 s 中查找是否存在与模式串 t 相等的子串。若存在返回首次匹配的位置,否则返回 -1。

BF 算法(朴素匹配 / Brute Force)

从主串 s 的第一个字符开始,与模式串 t 逐字符比较;若匹配失败,从 s 的下一个字符重新开始比较。时间复杂度 O(n·m)

int BF(String s, String t){
  int i=0, j=0;
  while(i<s.length && j<t.length){
    if(s[i]==t[j]){ i++; j++; }
    else{ i=i-j+1; j=0; }   // 回退
  }
  if(j==t.length) return i-j;   // 匹配成功
  return -1;                        // 匹配失败
}

BF 朴素匹配算法动画

交互式动画
速度 1x
步骤 0/0

4.4 KMP 算法

KMP 算法通过预处理模式串,构建 next 数组(也叫失配函数),在匹配失败时利用已匹配信息跳过不必要的比较,避免了主串指针的回退。时间复杂度 O(n+m)

1. next 数组的含义

next[j] 表示:当模式串 t 中第 j 个字符与主串中相应字符不匹配时,在模式串中需要重新与主串中该字符进行比较的位置。即 t[j] 前的子串 t[0..j-1] 的最长公共前后缀长度。

2. next 数组的计算

next[0] = -1;   // 特殊标记
for(j=1; j<m; j++){
  i = next[j-1];
  while(i>=0 && t[i]!=t[j-1]) i = next[i];
  next[j] = i + 1;
}

next 数组构建过程(动态演示)

交互式
速度1x

3. KMP 匹配过程

int KMP(String s, String t){
  int i=0, j=0;
  while(i<s.length && j<t.length){
    if(j==-1 || s[i]==t[j]){ i++; j++; }
    else{ j = next[j]; }   // 利用 next 跳过
  }
  if(j==t.length) return i-j;
  return -1;
}
BF vs KMP:BF 在匹配失败时回退主串指针,时间复杂度 O(n·m);KMP 通过 next 数组利用已匹配信息,主串指针永不回退,时间复杂度 O(n+m)。在实际应用中 KMP 效率更高。

KMP 算法匹配动画

交互式动画
速度 1x
步骤 0/0

4.5 各模式匹配算法对比

算法时间复杂度空间复杂度主串指针特点
BF 朴素匹配O(n·m)O(1)需要回退实现简单,实际效率尚可
KMPO(n+m)O(m)不回退利用 next 数组,适合长模式串
Boyer-MooreO(n+m)O(m+Σ)从右向左匹配实际中平均效率最高
Rabin-KarpO(n+m)O(1)不回退基于哈希,适合多模式匹配