在本文中，将使用始于零的数组来表示字符串。比如，若字符串`S = "ABC"`，则`S[2]`表示字符`'C'`。这种表示方法与C语言一致。
在计算机科学中，**克努斯-莫里斯-普拉特字符串查找算法**（英語：，简称为**KMP算法**）可在一个字符串`S`内查找一个`W`的出现位置。一个词在不匹配时本身就包含足够的信息来确定下一个匹配可能的开始位置，此算法利用这一特性以避免重新检查先前配對的字符。

这个算法由高德纳和沃恩·普拉特在1974年构思，同年詹姆斯·H·莫里斯也独立地设计出该算法，最终三人于1977年联合发表。

查找过程
----

以`W`=`"ABCDABD"`，`S`=`"ABC ABCDAB ABCDABCDABDE"`为例说明查找过程。查找过程同时使用两个循环变量`m`和`i`：

* `m`代表主文字符串`S`内匹配字符串`W`的当前查找位置，
* `i`代表匹配字符串`W`当前做比较的字符位置。

图示如下：

```
 1 2 
m: 01234567890123456789012
S: ABC ABCDAB ABCDABCDABDE
W: ABCDABD
i: 0123456
```

從`W`與`S`的開頭比較起。比對到`S[3](=' ')`時，發現`W[3](='D')`與之不符。接著並不是從`S[1]`比較下去。已經知道`S[1]~S[3]`不與`W[0]`相合。因此，略過這些字元，令`m = 4`以及`i = 0`。

```
 1 2 
m: 01234567890123456789012
S: ABC ABCDAB ABCDABCDABDE
W:     ABCDABD
i:     0123456
```

如上所示，檢核了`"ABCDAB"`這個字串。然而，下一字符便不相合。可以注意到，`"AB"`在`"ABCDAB"`的頭尾處均有出現。這意味著尾端的`"AB"`可以作為下次比較的起始點。因此，令`m = 8, i = 2`，繼續比較。圖示如下：

```
 1 2 
m: 01234567890123456789012
S: ABC ABCDAB ABCDABCDABDE
W:         ABCDABD
i:         0123456
```

於`m = 10`的地方，又出現不相符的情況。類似地，令`m = 11, i = 0`繼續比較：

```
             1         2  
m: 01234567890123456789012
S: ABC ABCDAB ABCDABCDABDE
W:            ABCDABD
i:            0123456
```

這時，`S[17](='C')`不與`W[6]`相同，但是已匹配部分`"ABCDAB"`亦为首尾均有`"AB"`，採取一貫的作法，令`m = 15`和`i = 2`，繼續搜尋。

```
             1         2  
m: 01234567890123456789012
S: ABC ABCDAB ABCDABCDABDE
W:                ABCDABD
i:                0123456
```

找到完全匹配的字串了，其起始位置於`S[15]`的地方。

部分匹配表
-----

**部分匹配表**，又称为**失配函数**，作用是让算法无需多次匹配`S`中的任何字符。能够实现线性时间搜索的关键是在主串的一些字段中检查模式串的*初始字段*，可以确切地知道在当前位置之前的一个潜在匹配的位置。换句话说，在不错过任何潜在匹配的情况下，"预搜索"这个模式串本身并将其译成一个包含所有可能失配的位置对应可以绕过最多无效字符的列表。

对于`W`中的任何位置，都希望能够查询那个位置前（不包括那个位置）有可能的`W`的最长初始字段的长度，而不是重新从`W[0]`开始比较整个字段，这长度就是查找下一个匹配时回退的距离。因此`T[i]`是`W`的可能的*适当*初始字段同时也是结束于`W[i - 1]`的子串的最大长度。使空串长度是0。当一个失配出现在模式串的最开始，这是特殊情况（无法回退），设置`T[0] = -1`，在下面讨论。

### 创建表算法示例

以`W = "ABCDABD"`为例。以下将看到，部分匹配表的生成过程与前述查找过程大同小异，且出于类似原因是高效的。

首先，设定`T[0] = -1`。为求出`T[1]`，必须找到一个`"A"`的真后缀（真后缀指不等于原串的后缀）兼`W`的前缀。但`"A"`没有真后缀，所以设定`T[1] = 0`。类似地，`T[2] = 0`。

继续到`T[3]`，注意到检查**所有**后缀有一个捷径：假设存在符合条件的前后缀，两者分别为`W[0..1] = W[1..2]`，则必有`W[0..0] = W[1..1]`。由于`W[0..0]`亦是`W`的真前缀，上一步必然已经得到`T[2] = 1`（而有`T[2] = 0`，说明假设不成立）。一般地，遍历到每个字符时，只有上一步已经发现一个长为m的有效后缀，才需要判断有无长为m+1的后缀，而毋需考虑长为m+2、m+3等的后缀。

从而，不必考虑长为2的后缀，而唯独需要考虑的长度1亦不可行，故得到`T[3]=0`。

接下来是`W[4] = 'A'`。基于同样的理由，需要考虑的最大长度为1，并且在`'A'`这个情况中有效，回退到寻找的当前字符*之前*的字段，因此`T[4] = 0`。

现在考虑下一个字符`W[5] = 'B'`，使用这样的逻辑：如果曾发现一个子模式在上一个字符`W[4]`之前出现，继续到当前字符`W[5]`，那么在它之前它本身会拥有一个结束于`W[4]`合适的初始段，与事实相反的是已经找到`'A'`是最早出现在结束于`W[4]`的合适字段。因此为了找到`W[5]`的终止串，不需要查看`W[4]`。因此`T[5] = 1`。

最后到`W[4] = 'A'`。下一个字符是`'B'`，并且这也确实是`W[5]`。此外，上面的相同参数说明为了查找`W[6]`的字段，不需要向前查看`W[4]`，所以得出`T[6] = 2`。

于是得到下面的表：

|  |  |  |  |  |  |  |  |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `i` | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
| `W[i]` | A | B | C | D | A | B | D |
| `T[i]` | -1 | 0 | 0 | 0 | 1 | 2 | 0 |

另一个更复杂和有趣的例子：

|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `i` | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| `W[i]` | P | A | R | T | I | C | I | P | A | T | E |  | I | N |  | P | A | R | A | C | H | U | T | E |
| `T[i]` | -1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 2 | 3 | 0 | 0 | 0 | 0 | 0 | 0 |

### 建立表算法的伪代码的解释

上面的例子以最少的复杂步骤展示了组织这个表格的一般性方法。这么做的原理是对整体的搜索：大多数工作已经在检测到当前位置的时候做完了，剩下需要做的很少。略微复杂的一点是找到一个共同前后缀。这就需要有一些初始化的代码。

```
**algorithm** *kmp\_table*:
    **input**:
        an array of characters, W (the word to be analyzed)
        an array of integers, T (the table to be filled)
    **output**:
        nothing (but during operation, it populates the table)

    **define variables**:
        an integer, pos ← 2 (the current position we are computing in T)
        an integer, cnd ← 0 (the zero-based index in W of the next   
character of the current candidate substring)

    (the first few values are fixed but different from what the algorithm   
might suggest)
    **let** T[0] ← -1, T[1] ← 0

    **while** pos < length(W) do
        (first case: the substring continues)
        **if** W[pos - 1] = W[cnd] **then**
            **let** cnd ← cnd + 1, T[pos] ← cnd, pos ← pos + 1

        (second case: it doesn't, but we can fall back)
        **else** **if** cnd > 0 **then**
            **let** cnd ← T[cnd]

        (third case: we have run out of candidates.  Note cnd = 0)
        **else**
            **let** T[pos] ← 0, pos ← pos + 1
```

### 建立表的算法的效率

建立表的算法的复杂度是{\displaystyle O(n)}，其中{\displaystyle n}是`W`的长度。

除去一些初始化的工作，所有工作都在循环中完成。为说明循环执行用了{\displaystyle O(n)}的时间，考虑`pos`和`pos - cnd`的大小。

* 在第一个分支里，`pos - cnd`不变，而`pos`与`cnd`同时自增，自然，`pos`增加了。
* 在第二个分支里，`cnd`被更小的`T[cnd]`所替代，从而增加了`pos - cnd`。
* 在第三个分支里，`pos`增加了，而`cnd`不变，所以`pos`和`pos - cnd`都增加了。

因为`pos ≥ pos - cnd`，即在每一个阶段要么`pos`增加，要么`pos`的一个下界增加。故既然算法在`pos = n`时终止，此循环必然在最多{\displaystyle 2n}次迭代后终止。因此建立表的算法的复杂度是{\displaystyle O(n)}。

另见
--

* Boyer-Moore字符串搜索算法

外部連結
----

* （英文）An explanation of the algorithm （页面存档备份，存于） and sample C++ code （页面存档备份，存于） by David Eppstein
* （英文）Knuth-Morris-Pratt algorithm （页面存档备份，存于） description and C code by Christian Charras and Thierry Lecroq
* （英文）Interactive animation for Knuth-Morris-Pratt algorithm by Mike Goodrich
* （英文）Explanation of the algorithm from scratch （页面存档备份，存于） by FH Flensburg.

引用
--

* 高德纳; James H. Morris, Jr, Vaughan Pratt.. SIAM Journal on Computing. 1977, **6** (2): 323–350  [2006-07-27]. （原始内容存档于2010-01-04）.  引文使用过时参数`coauthors` (帮助)

* Thomas H. Cormen; Charles E. Leiserson, Ronald L. Rivest, Clifford Stein..  Second edition. MIT Press and McGraw-Hill. 2001: 923-931. ISBN 978-0-262-03293-3.  引文使用过时参数`coauthors` (帮助) 引文格式1维护：冗余文本 (link)

|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 

| * 
* 论
* 编
算法 |
| --- |
|  |
| 排序 | 

|  |  |
| --- | --- |
| 比较排序 | 
* 冒泡排序
* 选择排序
* 插入排序
* 希尔排序
* 快速排序
* 归并排序
* 堆排序
* 鸡尾酒排序
* 梳排序
* 侏儒排序
* 图书馆排序
* 内省排序
* 奇偶排序

 |
|  |
| 线性时间排序 | 
* 鸽巢排序
* 基数排序
* 計數排序
* 桶排序

 |
|  |
| 并行排序 | 
* 排序网络（英语：）
* Batcher归并网络

 |
|  |
| 不实用的 | 
* Bogo排序
* 臭皮匠排序

 |
|  |
| 图 | 
* 拓撲排序

 |

 |
|  |
| 搜索 | 

|  |  |
| --- | --- |
| 列表 | 
* 线性搜索
* 二分搜索
* 插值搜尋

 |
|  |
| 树・图 | 
* 广度优先搜索
	+ 最良優先搜索（英语：）
	+ 均一开销搜索
	+ A\*
* 深度优先搜索
	+ 迭代深化深度优先搜索
	+ 深度限制搜索（日语：）
	+ 双向搜索
* 分枝限定法（英语：）

 |
|  |
| 字符串 | 
* KMP算法
* 博耶-穆尔字符串搜索算法
* AC自动机算法
* 拉宾-卡普算法
* bitap算法

 |

 |
|  |
| 最短路问题 | 
* 戴克斯特拉算法
* 贝尔曼-福特算法
* A\*搜尋演算法
* Floyd-Warshall算法

 |
|  |
| 最小生成树 | 
* 普林姆算法
* 克鲁斯克尔演算法

 |
|  |
| 最大流最小割 | 
* 福特-富尔克森算法
* 埃德蒙兹-卡普算法
* 迪尼茨算法

 |
|  |
| 线性规划 | 
* 单纯形法
* 卡马卡尔算法（英语：）

 |
|  |
| 順序統計量 | 
* 选择算法
* 中位数的中位数（英语：）

 |
|  |
| 種類 | 
* 精确算法
* 近似算法
* 随机化算法

 |
|  |
| 其他 | 
* 分治法
* 动态规划
* 贪心算法

 |
|  |
| **Category:算法** |

 |

|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 

| * 
* 论
* 编
字符串 |
| --- |
|  |
| String metric（英语：） | 
* 字符串近似匹配
* Bitap算法
* Damerau–Levenshtein距离
* 编辑距离
* 汉明距离
* Jaro–Winkler距离
* 李距离
* 莱文斯坦自动机
* 莱文斯坦距离
* Wagner–Fischer算法

 |
|  |
| 字符串搜索算法 | 
* Apostolico–Giancarlo算法
* 博耶-穆尔字符串搜索算法
* Boyer–Moore–Horspool算法
* KMP算法
* 拉宾-卡普算法

 |
|  |
| 多字符串搜索 | 
* AC自动机
* Commentz-Walter算法
* 拉宾-卡普算法

 |
|  |
| 正则表达式 | 
* 正则表达式引擎比较
* Regular tree grammar（英语：）
* 汤普森构造法
* 非确定有限状态自动机

 |
|  |
| 序列比对 | 
* Hirschberg's algorithm（英语：）
* 尼德曼-翁施算法
* 史密斯-沃特曼算法

 |
|  |
| 数据结构 | 
* DAFSA（英语：）
* 后缀数组
* 后缀自动机（英语：）
* 后缀树
* Generalized suffix tree（英语：）
* Rope（英语：）
* 三元搜索树

 |
|  |
| 其它 | 
* 语法分析
* 模式匹配
* Compressed pattern matching（英语：）
* 最长公共子序列
* 最长公共子串
* Sequential pattern mining（英语：）
* 字符串排序算法（英语：）

 |

 |

|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 

| * 
* 论
* 编
高德纳 |
| --- |
|  |
| 著作 | 
* 《计算机程序设计艺术》
* 《歌曲的計算複雜度（英语：）》
* 《電腦與排版（英语：）》
* 《具體數學》
* 《超現實數（英语：）》
* 《計算機科學家很少談論的事情（英语：）》
* 論文選集（英语：）

 |
|  |
| 软件 | 
* TeX
* METAFONT
* MIXAL
	+ MIX（英语：）
	+ MMIX（英语：）

 |
|  |
| 字型 | 
* AMS Euler
* Computer Modern
* Concrete Roman

 |
|  |
| 文学 | 
* WEB
* CWEB（英语：）

 |
|  |
|  | 
* X
* 高德納-本迪克斯補全（英语：）
* KMP
* 高德納洗牌（英语：）
* RSK 對應（英语：）
* TPK（英语：）
* 概念化戴克斯特拉
* Knuth's Simpath algorithm（英语：）

 |
|  |
| 相關 | 
* 舞蹈链
* 高德納獎勵支票（英语：）
* 高德納獎
* 高德納箭號表示法
* 编译器递归测试
* 2i進制
* -yllion（英语：）
* 《普茨比度量衡体系（英语：）》

 |

 |