字符串查找子串是计算机程序设计经常会遇到的难题。举个例子来说,有一个字符串"BBC ABCDAB ABCDABCDABDE",我想知道,里面是否包含另一个字符串"ABCDABD"?
完成这个任务有很多算法,KMP是最常用的算法之一,下面通过一个例子来详细描述一下这个算法:
1.
首先将子串与搜索串的第一个字符进行比较,因为不匹配,所以将子串向后移动直到
2.
当第一个字符相等时,再次比较第二个字符,直到
3.
此时,要查找的子串并没有找到,且与查找串对应的字符不相等。这时候,最自然的办法是将查找串中的下一个字符,重新开始,如下所示:
4.
虽然这样查找是可行的,但是效率是及其低下的,因为它需要将比较过的字符串再次进行比较。KMP算法的思想是,设法利用已经比较过的字符串信息,不要把搜索的位置移动到已经比较过的地方。KMP算法的核心是利用一张部分匹配的表,根据表的值,可以计算当匹配不成功的时候应该向后移动的位数。
5.
那么,如何计算这张表呢?首先应该了解两个概念,“前缀”和“后缀”,前缀指的是除了最后一个字符以外,一个字符串的全部头部的组合;后缀指的是除了第一个字符意外,一个字符串全部尾部的集合。而部分匹配表指的是前缀和后缀共有字符的最大长队。还是举这个例子:
- "A"的前缀和后缀都为空集,共有元素的长度为0;
- "AB"的前缀为[A],后缀为[B],共有元素的长度为0;
- ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
- "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
- "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;
- "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;
- "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0;
已知空格与D不匹配时,前面六个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符B对应的"部分匹配值"为2,因此按照下面的公式算出向后移动的位数:
移动位数 = 已匹配的字符数 - 对应的部分匹配值
因为 6 - 2 等于4,所以将搜索词向后移动4位。
7.
因为空格与C不匹配,搜索词还要继续往后移。这时,已匹配的字符数为2("AB"),对应的"部分匹配值"为0。所以,移动位数 = 2 - 0,结果为 2,于是将搜索词向后移2位。
8.
因为空格与A不匹配,继续后移一位。
9.
逐位比较,直到发现C与D不匹配。于是,移动位数 = 6 - 2,继续将搜索词向后移动4位。
10.
逐位比较,直到搜索词的最后一位,发现完全匹配,于是搜索完成。如果还要继续搜索(即找出全部匹配),移动位数 = 7 - 0,再将搜索词向后移动7位,这里就不再重复了。