"《算法导论》之‘字符串’":字符串匹配
2015-06-08 11:32
197 查看
本文主要叙述用于字符串匹配的KMP算法。
阮一峰的博文“字符串匹配的KMP算法"将该算法讲述得非常形象,可参考之。
代码看起来挺简洁,但要理解起来就比较麻烦了。其中有一个问题就是:字符串“cabab”的部分匹配值为什么全都为0?其中的“ab”不是重复了吗,应该要算作重复的子串啊?
之所以有这个困惑,是对字符串的前缀和后缀的概念不理解。在阮一峰的博文中,他提到"前缀"是指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。
另外他还举了一个例子来说明如何求取一个字符串的部分匹配值:
"部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例,
- "A"的前缀和后缀都为空集,共有元素的长度为0;
- "AB"的前缀为[A],后缀为,共有元素的长度为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。
[b]注意:这个例子个人感觉如果没有好好理解,则有可能误解!阮一峰的做法是将一个字符串拆成不同长度的子串,然后求部分匹配值,而我们实际并不是这样子做的(参考上边代码),所以这些步骤可能会让我们误解后缀一定要以该字符串的最后一个字符来结束。
对于一个字符串的前缀而言,一定要从第一个字符开始;而对其后缀而言,则不必要以最后一个字符结束。
注意,第19行的
不能直接写成
例如待匹配字串为abababc,匹配模式为aba,则有两个符合匹配模式的子串:aba、aba,而中间的a是共享的。
阮一峰的博文“字符串匹配的KMP算法"将该算法讲述得非常形象,可参考之。
字符串‘部分匹配值’计算
KMP算法重要的一步在于部分匹配值的计算。模仿《算法导论》中的伪代码,对应的C++代码为:vector<int> partialMatching(string P) { int szP = P.size(); vector<int> pMatch; pMatch.resize(szP); // retVec[0] = 0; int k = 0; for (int i = 1; i < szP; i++) { while (k > 0 && P[k] != P[i]) k = pMatch[k - 1]; if (P[k] == P[i]) k = k + 1; pMatch[i] = k; } return pMatch; }
代码看起来挺简洁,但要理解起来就比较麻烦了。其中有一个问题就是:字符串“cabab”的部分匹配值为什么全都为0?其中的“ab”不是重复了吗,应该要算作重复的子串啊?
之所以有这个困惑,是对字符串的前缀和后缀的概念不理解。在阮一峰的博文中,他提到"前缀"是指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。
另外他还举了一个例子来说明如何求取一个字符串的部分匹配值:
"部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例,
- "A"的前缀和后缀都为空集,共有元素的长度为0;
- "AB"的前缀为[A],后缀为,共有元素的长度为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。
[b]注意:这个例子个人感觉如果没有好好理解,则有可能误解!阮一峰的做法是将一个字符串拆成不同长度的子串,然后求部分匹配值,而我们实际并不是这样子做的(参考上边代码),所以这些步骤可能会让我们误解后缀一定要以该字符串的最后一个字符来结束。
对于一个字符串的前缀而言,一定要从第一个字符开始;而对其后缀而言,则不必要以最后一个字符结束。
KMP算法
模仿《算法导论》中的伪代码,对应的C++代码为:vector<int> KMPMatching(string T, string P) { int szT = T.size(); int szP = P.size(); vector<int> pMatch = partialMatching(P); vector<int> kmpMatch; int k = 0; for (int i = 0; i < szT; i++) { while (k > 0 && T[i] != P[k]) k = pMatch[k - 1]; if (T[i] == P[k]) k = k + 1; if (k == szP - 1) { kmpMatch.push_back(i - k + 1); k = pMatch[k - 1]; } } return kmpMatch; }
注意,第19行的
k = pMatch[k - 1];
不能直接写成
k = 0;
例如待匹配字串为abababc,匹配模式为aba,则有两个符合匹配模式的子串:aba、aba,而中间的a是共享的。
相关文章推荐
- [R语言绘图]plot函数的使用
- jsp 中useBean type与class的区别
- JS对URL字符串进行编码/解码分析
- vector排序问题<unresolved overloaded function type>
- sdut 6-2 多态性与虚函数
- vs无法识别的外部符号 main
- 为mysql master主机新建一slave并监控slave一致性
- Unity3d NGUI的使用(八)(NGUI 2DUI与3DObjects共存)
- Ubuntu 12.04 如何使用其他用户登录
- remove remove_if
- Unix下去掉^M的方法
- HIVE和Hbase区别
- onActivityResult 的用法
- 118 nginx+tomcat+session共享
- 将在一列的以逗号隔开的列数据转化为行数据显示的函数
- 通过 KVM+virt-manager配置双屏虚拟机(两套键盘。鼠标)
- Java批量生成Mac地址到文件
- CString与char[] 的相互转换方法以及结尾乱码问题的分析解决
- 字符编码详解
- Java中异常处理相关笔记