简单的模式匹配算法
指针StringPtr
和PatternPtr
分别指示主串String
和模式串Pattern
中当前正待比较的字符位置。
从主串String
的第一个字符起,与模式串Pattern
的第一个字符比较
- 若相等,则继续逐个比较后续字符
- 否则,从主串的下一个字符起,重新和模式的字符比较
以此类推,直至模式Pattern
中的每个字符依次和主串String
中的一个连续的字符序列相等,则
- 匹配成功,返回
String
中第一个相等字符的序号 - 否则匹配不成功,返回值为0
int Index(SString String, SString Pattern) {
int StringPtr = 1, PatternPtr = 1;
while (int StringPtr <= String.length && PatternPtr <= Pattern) {
if (String.ch[StringPtr] == Pattern.ch[PatternPtr]) {
++StringPtr; ++PatternPtr; // 继续比较后续字符
}else {
StringPtr = StringPtr - PatternPtr + 2;
PatternPtr = 1; // 指针回退
}
}
if (PatternPtr > Pattern.length)
return StringPtr - Pattern.length;
else return 0;
}
趟 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
一 | a | b | a | b | c | a | b | c | a | c | b | a | b |
a | b | ||||||||||||
二 | a | b | a | b | c | a | b | c | a | c | b | a | b |
三 | a | b | a | b | c | a | b | c | a | c | b | a | b |
a | b | c | a | ||||||||||
四 | a | b | a | b | c | a | b | c | a | c | b | a | b |
五 | a | b | a | b | c | a | b | c | a | c | b | a | b |
六 | a | b | a | b | c | a | b | c | a | c | b | a | b |
a | b | c | a | c |
效率分析
从第三趟的结果可知,主串中,第3、4、5和6个字符是a
、b
、c
、a
(与模式串已经匹配),由模式串自身可知:首字符a
与b
、c
不同,而与尾字符a
相同。所以第三趟失配后,主串指针回退到4的位置和模式串第一个字符a
开始比较,以及接下来的两次比较也是重复的,模式串在不断地进行自我比较。
可以直接比较主串7,和模式串2位置的字符(即第六趟的第二步)。所以就诞生了下面这个:
主串指针不回退,且模式串回退位数仅与自身结构有关的算法
KMP算法
基本概念
前缀:除去最后一个字符后,字符串所有的以第一个字符开头的头部子串 后缀:除去第一个字符后,字符串所有的以最后一个字符结尾的尾部子串 部分匹配值:字符串的前缀和后缀的最长相等前后缀长度
原理
计算模式串的部分匹配值(Partial Match)的表
p | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
pattern | a | b | c | a | c |
PM | 0 | 0 | 0 | 1 | 0 |
模式串p
位置,与主串当前位置s
的字符不匹配时,
子串(模式串)需要向后移动的位数 = 已匹配的字符数 - 对应的部分匹配值
即:Move = p - PM[p-1]
s | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
string | a | b | a | b | c | a | b | c | a | c | b |
一 | a | b | |||||||||
二 | a | b | c | a | |||||||
yes | a | b | c | a | c |
以第二次匹配为例,主串的b与模式串的c不匹配,
已匹配的 'abca' 的前缀a和后缀a为最长公共元素,已知a与b、c均不同,与后缀a相同,故a与主串b、c无须再重复比较,直接将子串移动Move
个位置,用子串前缀后面的元素与主串匹配失败的元素开始比较即可。
改进
next数组
p
位置匹配失败,要查看PM[p-1]
,不方便,于是将PM表右移一位,得到next数组
p | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
pattern | a | b | c | a | c |
next | -1 | 0 | 0 | 0 | 1 |
注意:
- 若是第一个元素匹配失败,需要将模式串右移一位,所以第一个元素右移后的空缺用-1填充。
- 最后一个元素溢出,但最后一个元素的部分匹配值时给下一个元素使用的,显然没有下一个元素,故可以舍去。
此时:Move = p-next[p]
程序表述
计算机编程实现前述的模式串右移Move
个位置过程,表达为指针p
回退到新的位置,即:p = p-Move = p-(p-next[p]) = next[p]
next[p]
的含义:在子串的第p
个字符与主串发成失配时,跳到子串的next[p]
位置重新与主串当前位置进行比较