数据结构 · 2027 考研计算机 408

第 4 章 串(KMP 模式匹配)

目标院校:四川大学 / 电子科技大学 | 建议用时:概念 2 小时 + 例题练习 3 小时

本章地位:串是内容受限的线性表,在 408 中分值不高(选择 0~2 分),但next 数组手算几乎必考选择题——给模式串求 next / nextval、问匹配趟数或比较次数,是数据结构中最稳定的「送分计算题」。核心只有两件事:① 把 next 的定义「最长相等前后缀长度 + 1」背到条件反射;② 用两个具体模式串把 BF 的回溯和 KMP 的右滑各走一遍。本章所有手算结果都逐步列核对表。

4.1 串的定义与存储

4.1.1 基本概念:空串、子串与位置

定义串(字符串)是由零个或多个字符组成的有限序列,记作 \[ s='a_1a_2\cdots a_n'\quad(n\ge 0) \] 其中 \(a_i\) 为字符,\(n\) 为串的长度。\(n=0\) 的串称为空串,记作 \(\varnothing\) 或 ''。串中任意个连续字符组成的子序列称为该串的子串(空串与串本身也是子串),包含子串的串称为主串。
位置与相等① 字符在串中的位置:该字符在序列中的序号(408 惯例从 1 数起);
② 子串在主串中的位置:子串第一个字符在主串中的位序;
③ 两个串相等:当且仅当它们的长度相等且对应位置上的字符都相同。
例:\(s=\) 'ababcabcacbab'(长 13)中,子串 'abcac' 出现的位置是 6(其首字符 a 是主串第 6 个字符)。
子串计数公式长度为 \(n\) 的串,非空子串共 \[ \underbrace{n+(n-1)+\cdots+1}_{\text{按长度分组}}=\frac{n(n+1)}{2}\ \text{个} \] 再加空串共 \(\frac{n(n+1)}{2}+1\) 个。验证:'abc' 的非空子串为 a、b、c、ab、bc、abc 共 \(\frac{3\times4}{2}=6\) 个 ✓,含空串共 7 个 ✓(若字符互不相同;有重复字符时「本质不同」子串要去重,题目一般会说明)。
易错① 空串 ≠ 空格串:' '(3 个空格)长度为 3,是内容全为空格的非空串;空串长度为 0;
② 'abc' 与 'abc '(尾部多一个空格)不相等——长度不同即否;
③ 子串必须连续:'ac' 不是 'abc' 的子串(跳过 b 的叫子序列,不是子串)。
例 1 高频考点 串的基本概念辨析

下列关于串的叙述中,错误的是( )
A. 空串是任何串的子串 B. 串 'abc' 的非空子串共有 6 个 C. 空格串的长度为零 D. 两个串相等当且仅当二者长度相等且对应位置字符相同

查看解答

C。逐项分析:

A 对:按定义子串包含空串(“任意个连续字符”含零个);空串也是任何串的真子串。

B 对:'abc' 的非空子串 a、b、c、ab、bc、abc 共 6 个 \(=\frac{3\times4}{2}\) ✓。

C 错:空格串由一个或多个空格字符组成,长度等于空格个数;长度为零的是空串。

D 对:这正是串相等的定义(先比长度,再逐位比字符)。

练习 1 易错

判断正误:(1) 'ab' 是 'aabb' 的子串;(2) 串 ' data '(首尾各 1 个空格)与 'data' 相等;(3) 空串是线性结构。

查看答案

(1) 错:'aabb' 的长度为 2 的子串只有 aa、ab、bb,'ab' 之后无法再取出连续的 'ab'(子序列才可以)。

(2) 错:长度 6 ≠ 4,空格也是字符。

(3) 对:串(含空串)是元素受限(字符)的线性表,逻辑结构为线性结构。

4.1.2 存储结构:顺序(定长 / 堆分配)与块链

三种存储结构
  1. 定长顺序存储:用固定长度数组存字符,多余位置补 '\0' 或另设 length 字段记录实际长度——超过预定义长度的串值被截断(“截断”是定长串特有现象);
  2. 堆分配存储:仍用一组地址连续的存储单元,但空间在程序执行时按串长动态分配(malloc / realloc),克服截断;
  3. 块链存储:链式存储,每个结点可存多个字符(结点中字符个数称结点大小),最后一个结点不满时用 '#' 或空格填充。

// 定长与堆分配顺序串的类型定义对比

#define MAXLEN 255
typedef struct {          // ① 定长顺序串:空间固定,超长截断
    char ch[MAXLEN];
    int  length;
} SString;
typedef struct {          // ② 堆分配串:运行时按需申请
    char *ch;             // 指向动态分配的存储区
    int  length;
} HString;
存储密度块链存储中 \[ \text{存储密度}=\frac{\text{串值所占存储位}}{\text{实际分配的存储位}} \] 结点大小越大,密度越高(运算稍慢:取字符可能要跨越结点);结点越小,运算灵活但指针开销大。典型数值(设字符 1 B、指针 4 B):结点大小 1 时密度 \(=\frac{1}{1+4}=20\%\);结点大小 4 时 \(=\frac{4}{4+4}=50\%\);结点大小 80 时 \(=\frac{80}{80+4}\approx95\%\)。
例 2 方法 块链存储密度计算

设字符占 1 B、指针占 4 B。用结点大小为 4 的块链存储一个长度为 100 的串,共需多少结点?实际占用多少字节?存储密度是多少?

查看解答

结点数 \(=\lceil100/4\rceil=25\) 个;每结点实际占 \(4+4=8\) B,共 \(25\times8=200\) B。

\[ \text{密度}=\frac{100\times1}{200}=50\%\quad\Big(=\frac{4}{4+4}\Big)\ \checkmark \]

与公式一致:结点整除填满时密度只取决于结点大小,与串长无关。若改用结点大小 80:\(\lceil100/80\rceil=2\) 个结点占 \(2\times84=168\) B,密度 \(\frac{100}{168}\approx60\%\)(最后一个结点浪费 60 个字符位,短串时大结点反而不划算)。

练习 2

(1) 串 'abcde' 的非空子串有几个?含空串呢? (2) 定长顺序串 'abcdefghij'(MAXLEN = 8)实际存下的是什么?

查看答案

(1) 非空子串 \(\frac{5\times6}{2}=15\) 个(验证:按长度 \(5+4+3+2+1=15\) ✓);含空串共 16 个。

(2) 只能存前 8 个字符 'abcdefgh',后面 'ij' 被截断——这是定长顺序存储的固有缺陷,堆分配存储不存在此问题。

4.1.3 串的基本运算:与线性表的差别

基本运算清单赋值 StrAssign、复制 StrCopy、求长 StrLength、判空 StrEmpty、比较 StrCompare(字典序)、联接 Concat、求子串 SubString(给起点与长度)、定位 Index(模式匹配)、替换 Replace、插入 StrInsert、删除 StrDelete、清空 ClearString。
与线性表的差别线性表的操作对象多为单个元素(取第 i 个、在 i 处插入),串的操作对象是整串或子串(求子串、联接、定位、替换),基本没有“访问串中第 i 个字符”这类原子操作。定位运算 Index 就是 4.2 / 4.3 的主题——模式匹配。

4.2 BF 简单模式匹配 方法

4.2.1 算法思想:失配后主串指针回溯

模式匹配模式匹配:在主串 \(S\) 中找到子串 \(T\)(称模式串)的过程,即实现定位运算 Index。\ BF(Brute-Force,朴素 / 简单)算法:从主串第 pos 个字符起与模式串第 1 个字符比较,若相等则二者指针同步后移继续比较;失配时,主串指针回溯到本趟起点的下一位置,模式串重新从头比较: \[ i=i-j+2,\qquad j=1 \] (失配时本趟起点为 \(i-j+1\),下一趟从 \(i-j+2\) 开始。)
主串 S 1 2 3 4 5 6 7 8 a a a a a a a b n = 8 第 1 趟 a✓ a✓ b✗ i 由 3 回溯到 2,j 重置 1 第 2 趟 a✓ a✓ b✗ 第 3~5 趟同理,各比较 3 次后失配 第 6 趟 a✓ a✓ b✓ 匹配成功,返回位置 6 T = 'aab'(m = 3),共 6 趟 × 3 次 = 18 次比较
图 4-1 BF 匹配的回溯过程(S='aaaaaaab',T='aab'):每趟失配后主串指针退回「本趟起点 + 1」,模式串从头再来——已比较过的信息全部作废

4.2.2 最坏时间复杂度 \(O(nm)\) 及验证

最坏情况每趟都在模式串的最后一个字符处失配('aaa…ab' 型主串配 'a…ab' 型模式),每趟比较 \(m\) 次,主串起点共可取 \(n-m+1\) 个,总比较次数 \[ (n-m+1)\times m=O(n\times m) \] 其中 \(n\)、\(m\) 为主串与模式串长度(\(m\ll n\) 时约 \(O(nm)\))。最好情况(首趟即成功)为 \(O(m)\)。
数值验证取 \(S=\) 'aaaaaaab'(\(n=8\)),\(T=\) 'aab'(\(m=3\)):第 1~5 趟(起点 1~5)都是在 \(T_3=\) 'b' 处与 \(S\) 的 'a' 失配,各 3 次;第 6 趟(起点 6)\(S_6\)a✓、\(S_7\)a✓、\(S_8\)b✓ 成功,3 次。合计 \(5\times3+3=18=(8-3+1)\times3\) ✓。

// BF 简单模式匹配:位序从 1 开始,返回 T 在 S 中首次出现的位置,失败返回 0

int BF(char S[], char T[], int n, int m) {
    int i = 1, j = 1;
    while (i <= n && j <= m) {
        if (S[i] == T[j]) {
            ++i; ++j;              // 当前字符相等,继续比较后继
        } else {
            i = i - j + 2;         // 主串指针回溯到本趟起点的下一位置
            j = 1;                 // 模式串回到第 1 个字符
        }
    }
    if (j > m) return i - m;       // 匹配成功:本趟起点
    else       return 0;           // 匹配失败
}
例 3 BF 最坏比较次数计算

主串 \(S=\) 'aaaaaaaaab'(长 10),模式串 \(T=\) 'aaab'(长 4)。求 BF 算法匹配成功前的总比较次数。

查看解答

起点可取 \(10-4+1=7\) 个;每趟前 3 个 'a' 全部配对,第 4 次比较 \(T_4=\) 'b' 与主串字符:起点 1~6 处均为 'a',失配(各 4 次);起点 7 时 \(S_7\)a✓、\(S_8\)a✓、\(S_9\)a✓、\(S_{10}\)b✓ 成功(4 次)。

\[ 7\times4=28\ \text{次}=(n-m+1)\times m\ \checkmark \]

套路总结:'aaa…a b' 型主串 + 'a…ab' 型模式 = BF 最坏输入,每趟必比满 m 次再失败。

练习 3

主串长 \(n=100\)、模式串长 \(m=5\)。分别求 BF 算法「首趟即匹配成功」与「最坏情况」的比较次数。

查看答案

最好:\(m=5\) 次(一次比到底)。最坏:\((100-5+1)\times5=96\times5=480\) 次 ✓。

4.3 KMP 模式匹配 高频考点

动机BF 慢在回溯:第 k 趟失配时,主串中刚比较过的那些字符其实是「已知的」,白白丢掉。KMP 算法利用模式串自身的结构信息:失配时主串指针 \(i\) 绝不回溯,模式串向右滑动到合适位置后继续比较。滑动多远,由模式串的 next 数组决定。

4.3.1 next 数组:定义与手算

next 数组定义(位序从 1)当模式串 \(t\) 的第 \(j\) 个字符与主串失配时,主串当前位置应与模式串的第 next[j] 个字符继续比较,且 \[ \mathrm{next}[j]=\big(t[1..j-1]\ \text{中最长相等前后缀的长度}\big)+1 \] 约定 \(\mathrm{next}[1]=0\)(第 1 个字符就失配 ⇒ 模式串整体右移一位,\(i\)、\(j\) 同时前进 1);\(\mathrm{next}[2]=1\)(\(t[1..1]\) 无真前后缀,\(0+1=1\))。由此必有 \(\mathrm{next}[j]\le j-1\)。
手算三步求 \(\mathrm{next}[j]\):① 写出 \(t[1..j-1]\);② 找它的「头 = 尾」最大重合(前缀从头往后、后缀从尾往前,不许取整段);③ 该重合长度 + 1。

演示一:\(t=\) 'abaabc'。逐步核对表:

j123456
t[j]abaabc
前缀 t[1..j−1]—aababaabaaabaab
最长相等前后缀—无无aaab
next[j]011223

抽查核对:j = 6 时看 'abaab',头 2 个 'ab' 与尾 2 个 'ab' 相等(长 2),头 3 个 'aba' 与尾 3 个 'aab' 不等,故 next[6] = 2 + 1 = 3 ✓;j = 5 时 'abaa' 头 'a' = 尾 'a'(长 1),头 'ab' ≠ 尾 'aa',故 next[5] = 2 ✓。

演示二:\(t=\) 'ababaa'。逐步核对表:

j123456
t[j]ababaa
前缀 t[1..j−1]—aababaababababa
最长相等前后缀—无无aababa
next[j]011234

抽查核对:j = 6 时 'ababa',头 'aba' = 尾 'aba'(长 3),故 next[6] = 4 ✓;j = 5 时 'abab',头 'ab' = 尾 'ab'(长 2),故 next[5] = 3 ✓。

4.3.2 nextval:next 数组的再优化

修正规则若按 next[j] 滑动后要比较的字符 \(t[\mathrm{next}[j]]\) 与失配字符 \(t[j]\) 相同,则这次比较必然再次失配(对手还是刚才那个主串字符),应继续向前修正: \[ \mathrm{nextval}[j]=\begin{cases}\mathrm{nextval}\big[\mathrm{next}[j]\big], & t[j]=t[\mathrm{next}[j]]\\ \mathrm{next}[j], & t[j]\ne t[\mathrm{next}[j]]\end{cases} \] 求 nextval 必须先有 next,且从左往右递推(右边要用已修正的值)。

对上述两串求 nextval(t[next[j]] 一行是比较依据,"—"表示 next[j]=0 无从比较,nextval[1] 恒为 0):

t = 'abaabc'j=1j=2j=3j=4j=5j=6
next[j]011223
t[j] 与 t[next[j]]—b≠aa=ab=ba≠bc≠a
nextval[j]010123
t = 'ababaa'j=1j=2j=3j=4j=5j=6
next[j]011234
t[j] 与 t[next[j]]—b≠aa=ab=ba=aa≠b
nextval[j]010104

核对示例:'abaabc' 的 j=3,next[3]=1 且 t[3]='a'=t[1]='a',故 nextval[3]=nextval[1]=0 ✓;j=4,next[4]=2 且 t[4]='b'=t[2]='b',故 nextval[4]=nextval[2]=1 ✓。'ababaa' 的 j=6,next[6]=4 但 t[6]='a'≠t[4]='b',故 nextval[6]=next[6]=4 ✓。

4.3.3 匹配过程演练:主串指针不回溯

主串 \(S=\) 'ababcabcacbab'(\(n=13\)),模式串 \(T=\) 'abcac'(\(m=5\))。先手算 next:

j12345
t[j]abcac
最长相等前后缀—无无无a
next[j]01112

逐趟匹配(每趟给起点与失配位):

  1. 第 1 趟(起点 1):\(S_1\)a=\(T_1\) ✓,\(S_2\)b=\(T_2\) ✓,\(S_3\)a≠\(T_3\)c 失配(\(i=3,j=3\))→ \(j=\mathrm{next}[3]=1\),\(i\) 不动。本趟 3 次。
  2. 第 2 趟(起点 3):\(S_3\)a ✓,\(S_4\)b ✓,\(S_5\)c ✓,\(S_6\)a ✓(比到 \(T_4\)),\(S_7\)b≠\(T_5\)c 失配(\(i=7,j=5\))→ \(j=\mathrm{next}[5]=2\),\(i\) 不动。本趟 5 次。
  3. 第 3 趟(起点 6):\(S_7\)b=\(T_2\) ✓,\(S_8\)c=\(T_3\) ✓,\(S_9\)a=\(T_4\) ✓,\(S_{10}\)c=\(T_5\) ✓ → \(j=6>m\),成功,位置 \(=i-m=11-5=6\)(\(S[6..10]=\) 'abcac' ✓)。本趟 4 次。

总比较 \(3+5+4=12\) 次 ✓;主串指针 \(i\) 的轨迹 \(1\to2\to\cdots\to11\) 单调前进、从不回退——这正是 KMP 的标志。同一对串 BF 需比较 16 次才成功。

主串 S a b a b c a b c a c b a b 1 3 6 8 11 失配时 a✓ b✓ c✓ a✓ c✗ S₇≠T₅,失配(j=5) 右滑 j−next[j]=3 位 右滑后 a b c a c 灰格 'a' 由「最长相等前后缀」自动对齐, 无需重比;从 T 的第 next[5]=2 个字符继续
图 4-2 KMP 失配时模式串右滑示意:已匹配段 'abca' 的最长相等前后缀 'a'(长 1)滑到与主串尾部 'a' 对齐,主串指针 i 停在 7 不动

4.3.4 KMP 代码、复杂度与易错点

// 求 next 数组 + KMP 匹配(位序从 1 开始,next[1] = 0)

void get_next(char T[], int next[], int m) {
    int i = 1, j = 0;
    next[1] = 0;                    // 约定
    while (i < m) {
        if (j == 0 || T[i] == T[j]) {
            ++i; ++j;
            next[i] = j;            // t[1..i-1] 最长相等前后缀 + 1
        } else
            j = next[j];            // 前缀指针回退(递推求法)
    }
}

int KMP(char S[], char T[], int next[], int n, int m) {
    int i = 1, j = 1;
    while (i <= n && j <= m) {
        if (j == 0 || S[i] == T[j]) {
            ++i; ++j;               // 主串指针只前进,从不回溯
        } else
            j = next[j];            // i 不动,模式串右滑
    }
    if (j > m) return i - m;        // 成功:返回起点位序
    else       return 0;
}
复杂度求 next:\(i\) 只增加不减少,均摊 \(O(m)\);匹配:\(i\) 每轮前进、\(j\) 的减少量不超过 \(i\) 的增加量,均摊 \(O(n)\)。故 \[ T(n)=O(m+n),\qquad S(n)=O(m)\ \text{(next 数组)} \] 用 4.3.3 的例子核对:求 next 比较 5 次量级 + 匹配 12 次,主串指针始终向前 ✓。
易错:位序从 1 还是从 0408(王道)惯例位序从 1:next[1]=0、next[2]=1,next[j]=最长相等前后缀长+1。若题目声明「下标从 0 开始」,常约定 next[0]=−1,此时把 1 起始的数组整体左移一位(如 'abaabc' 从 0 1 1 2 2 3 变为 −1 0 1 1 2 2)。本质(滑动量 = 最长相等前后缀)不变,答题前先看题目给的下标约定,选择题按题目定义逐位对号,切莫拿背熟的数组直接套。
例 4 高频考点 给串求 next 与 nextval(两小问)

模式串 \(t=\) 'ababab'(位序从 1)。(1) 求 next 数组;(2) 在此基础上求 nextval 数组。

查看解答

(1) 逐步算最长相等前后缀:'a' 无;'ab' 无;'aba' 有 'a'(长 1);'abab' 有 'ab'(长 2);'ababa' 有 'aba'(长 3)。各 +1:

next = 0 1 1 2 3 4 (核对 j=6:'ababa' 头尾 'aba' 相等,3+1=4 ✓)

(2) 逐位判断 \(t[j]\) 与 \(t[\mathrm{next}[j]]\):j=2:b≠a → 1;j=3:a=a → nextval[1]=0;j=4:b=b → nextval[2]=1;j=5:a=a → nextval[3]=0;j=6:b=b → nextval[4]=1。

nextval = 0 1 0 1 0 1 ✓(周期串的 nextval 呈 0、1 交替,验证了「重复字符必再失配」的直觉)

例 5 真题风格 KMP 匹配趟数与比较次数

主串 \(S=\) 'abababaa'(\(n=8\)),模式串 \(T=\) 'ababaa'(\(m=6\)),用 KMP(next = 0 1 1 2 3 4)匹配。问共几趟、比较总次数多少?并与 BF 对比。

查看解答

第 1 趟(起点 1):\(S_1\cdots S_5\) 与 \(T_1\cdots T_5\) 全配对(5 次),\(S_6\)b≠\(T_6\)a 失配(1 次),\(i=6,j=6\) → \(j=\mathrm{next}[6]=4\),共 6 次。

第 2 趟(起点 \(6-4+1=3\)):\(S_6\)b=\(T_4\)b ✓,\(S_7\)a=\(T_5\)a ✓,\(S_8\)a=\(T_6\)a ✓ → 成功,位置 3(\(S[3..8]=\) 'ababaa' ✓),共 3 次。

共 2 趟、9 次比较 ✓。BF:起点 1 比 6 次失配,起点 2 比 5 次失配,起点 3 比 6 次成功,共 17 次——KMP 省下的正是主串指针反复回溯的重复劳动。

例 6 next 数组性质(选择)

位序从 1 开始。下列关于 next 数组的叙述中,正确的是( )
A. next[j] 等于 t[1..j−1] 中最长相等前后缀的长度 B. 对任何模式串都有 next[1]=0、next[2]=1 C. 存在模式串使 next[j]=j D. nextval[j] 一定小于 next[j]

查看解答

B。逐项分析:

A 错:next[j] = 最长相等前后缀长度 +1(如 'ababaa' 的 next[6]=4,而最长前后缀长 3)。

B 对:均为约定值,与串内容无关。

C 错:t[1..j−1] 的真前缀至多长 \(j-2\),故 next[j] ≤ \(j-1\),取不到 \(j\)('aaaa' 也只有 next[4]=3)。

D 错:t[next[j]]≠t[j] 时二者相等(如 'abaabc' 的 j=5:next[5]=nextval[5]=2)。

练习 4 高频考点

分别求模式串 'aaaaa' 与 'ababa'(位序从 1)的 next 与 nextval 数组。

查看答案

'aaaaa':前后缀逐层相等,next = 0 1 2 3 4;因相邻字符全同,nextval 全部回溯到源头,nextval = 0 0 0 0 0(核对 j=5:t[5]=a=t[next[5]=4]=a → nextval[4] → … → nextval[1]=0 ✓)。

'ababa':next = 0 1 1 2 3;nextval:j=2 b≠a→1,j=3 a=a→0,j=4 b=b→1,j=5 a=a→0,即 nextval = 0 1 0 1 0。

4.4 章末自测 真题风格

限时 40 分钟,先做后看答案。难度:★★ 基础 / ★★★ 强化 / ★★★★ 冲刺。所有 next / nextval 手算结果务必逐位核对(用 j=末位抽查一次)。

自测 1(选择 · ★★)

设 s₁ 为空串,s₂ = '  '(2 个空格)。下列说法正确的是( )
A. s₁ 与 s₂ 相同 B. s₂ 的长度为 0 C. s₁ 长度为 0 而 s₂ 长度为 2 D. s₂ 是空串

查看答案

C。空格也是字符:空串长 0,空格串长 = 空格个数;二者只在「长度」上都与 2 无关的 A、B、D 全错。

自测 2(填空 · ★★)

串 'abcde' 的子串个数(含空串)共 \(\underline{\hspace{1.5cm}}\) 个。

查看答案

\(\frac{5\times6}{2}+1=16\) 个(按长度 \(5+4+3+2+1=15\) ✓,再加空串)。

自测 3(手算 · ★★★ 高频考点)

求模式串 'ababaab'(位序从 1)的 next 与 nextval 数组。

查看答案

next = 0 1 1 2 3 4 2 (核对 j=7:'ababaa' 头尾只有 'a'='a' 相等,1+1=2 ✓)

nextval = 0 1 0 1 0 4 1 (j=6:a≠t[4]=b → 保留 4;j=7:b=t[2]=b → 取 nextval[2]=1 ✓)

自测 4(手算 · ★★★ 高频考点)

求模式串 'aabaaa'(位序从 1)的 next 与 nextval 数组。

查看答案

next = 0 1 2 1 2 3 (核对 j=3:'aa' 头 'a'=尾 'a',1+1=2 ✓;j=6:'aabaa' 头 'aa'=尾 'aa',2+1=3 ✓)

nextval = 0 0 2 0 0 3 (j=2:a=a → 取 nextval[1]=0;j=3:b≠a → 保留 2;j=6:a≠t[3]=b → 保留 3 ✓)

自测 5(手算 · ★★★)

求模式串 'abcabd'(位序从 1)的 next 与 nextval 数组。

查看答案

next = 0 1 1 1 2 3 (核对 j=6:'abcab' 头 'ab'=尾 'ab',2+1=3 ✓)

nextval = 0 1 1 0 1 3 (j=4:a=a → 取 nextval[1]=0;j=6:d≠t[3]=c → 保留 3 ✓)

自测 6(选择 · ★★★)

主串长 \(n=8\)、模式串长 \(m=3\),BF 算法最坏情况下的比较次数是( )
A. 8 B. 18 C. 24 D. 11

查看答案

B。\((n-m+1)\times m=6\times3=18\) 次(即 4.2 节 S='aaaaaaab'、T='aab' 的逐步核对结果 ✓)。

自测 7(选择 · ★★)

KMP 算法(含求 next)的时间与空间复杂度为( )
A. \(O(nm)\)、\(O(1)\) B. \(O(m+n)\)、\(O(m)\) C. \(O(m+n)\)、\(O(n)\) D. \(O(n\log_2 m)\)、\(O(m)\)

查看答案

B。求 next \(O(m)\) + 匹配 \(O(n)\);辅助空间是长度 \(m\) 的 next 数组(与主串长无关,C 错)。

自测 8(选择 · ★★★ 易错)

下列关于 KMP 算法的叙述中,错误的是( )
A. 匹配过程中主串指针始终不回溯 B. next 值只与模式串自身有关 C. 使用 nextval 可能使比较次数进一步减少 D. KMP 最坏时间复杂度为 \(O(nm)\)

查看答案

D。KMP 最坏也是 \(O(m+n)\)(这正是它相对 BF 的意义);A、B、C 均为 KMP 的基本性质。

自测 9(填空 · ★★★★ 冲刺)

块链存储中字符占 1 B、指针占 4 B。要使存储密度不低于 80%,结点大小至少为 \(\underline{\hspace{1.5cm}}\)。

查看答案

设结点大小为 \(k\):\(\frac{k}{k+4}\ge0.8\Rightarrow k\ge16\),即至少 16(验证 \(\frac{16}{16+4}=80\%\) ✓;\(k=12\) 时仅 \(\frac{12}{16}=75\%\) 不达标)。

4.5 本章考点总结

考点常考题型热度核心方法
串的概念(空串 / 空格串 / 子串 / 相等)选择题★★★空格也是字符;子串必须连续;子串数 \(\frac{n(n+1)}{2}+1\)
存储结构(定长 / 堆分配 / 块链)选择 / 填空★★定长会截断;存储密度 \(=\frac{\text{串值位}}{\text{实际分配位}}\),结点越大密度越高
BF 最坏比较次数选择 / 填空★★★★\((n-m+1)\times m\),'aaa…ab' 型输入触发;失配回溯 \(i=i-j+2\)
手算 next 数组选择 / 解答★★★★★ 几乎必考看 \(t[1..j-1]\) 找最长相等前后缀再 +1;next[1]=0、next[2]=1;next[j]≤j−1
手算 nextval 数组选择 / 解答★★★★先有 next 再从左修正:\(t[j]=t[\mathrm{next}[j]]\) 则取 nextval[next[j]],否则保留 next[j]
KMP 匹配过程(趟数 / 比较次数 / i 不回溯)选择 / 解答★★★★失配时 \(j=\mathrm{next}[j]\)、\(i\) 不动,右滑 \(j-\mathrm{next}[j]\) 位;复杂度 \(O(m+n)\)、空间 \(O(m)\)
下标约定(从 1 / 从 0)选择(陷阱项)★★★408 惯例从 1:0 1 1 2 …;从 0 则整体左移一位,答题前先看题目约定
下一步本章过关标准:任给模式串 30 秒内默写 next 与 nextval 并用末位抽查核对;能复述 BF 的 \(i=i-j+2\) 与最坏 \((n-m+1)m\)、KMP 的 \(O(m+n)\);能完整走一遍 'ababcabcacbab' 对 'abcac' 的三趟匹配。然后进入 第 5 章 树与二叉树——408 数据结构选择题与大题的双料核心区。