之前对于字符串模式匹配的了解仅限于那几个API函数和暴力求解方法,没有真正地探讨过关于字符串匹配的问题,今天因为一些原因看到了这个问题引发了我的兴致,故研究了一下sunday算法。
1.sunday思路
sunday算法是从前往后匹配,在匹配失败时关注的是源字符串参与匹配最末尾字符的下一个字符:
- 如果该字符没有在模式串中出现则直接跳过,匹配移动位数=模式串长度+1
- 否则,匹配移动位数=模式串中最右端的该字符到模式串末尾的距离+1
2.实例
源字符串:abcdefghijk
模式串:hijk
1.第一步,左对齐,依次比较字符。
a | b | c | d | e | f | g | h | i | j | k |
---|---|---|---|---|---|---|---|---|---|---|
h | i | j | k |
2.发现模式串首位(h)与源串首位(a)无法匹配,源字符串参与匹配最末尾字符的下一个字符(e)不存在于模式串中(hijk),并且将模式串向后移动(模式串长度4+1)位。(f和h对齐)
a | b | c | d | e | f | g | h | i | j | k |
---|---|---|---|---|---|---|---|---|---|---|
h | i | j | k |
3.发现模式串首位(h)与源串首位(f)无法匹配,源字符串参与匹配最末尾字符的下一个字符(j)存在于模式串中(hijk),将模式串向后移动(模式串中最右端的该字符到模式串末尾的距离(4-3)+1)。(两个j对齐)
a | b | c | d | e | f | g | h | i | j | k |
---|---|---|---|---|---|---|---|---|---|---|
h | i | j | k |
4.发现模式串首位(h)与源串首位(h)匹配,继续遍历发现所有字符匹配,匹配成功。
3.Java实现
public class Sunday {
/**
* 初始化一个大小为256的数组,用于存储在模式串中最后出现的字符下标
* 因为源字符串参与匹配最末尾字符的下一个字符可能在模式串中出现多次,而我们需要的是最右边的位置
* 我们遍历一遍模式串,将字符下标存到数组中,便可以在O(1)的时间复杂度下找到最右位置,不需要每次遍历寻找
* 实际上就是模式串后面如果出现重复字符会进行覆盖,所以我们每次取到的都是最右的位置。
*/
static int[] map = new int[256];
public static int sundaySearch(String source, String pattern) {
int sLen = source.length();//源串长度
int pLen = pattern.length();//模式串长度
//数组初始化为-1是为第1节中我们讲到的第1种情况做准备
//这块我们应该结合k += (pLen - map[source.charAt(k + pLen)])进行说明
for (int i = 0; i < 256; i++) {
map[i] = -1;
}
//在相应的字符位标注模式串下标
for (int i = 0; i < pLen; i++) {
map[pattern.charAt(i)] = i;
}
//从源串首位进行匹配,当k+pLen大于sLen时停止匹配
for (int k = 0; k <= sLen - pLen;) {
int i = k;//源串匹配首位
int j = 0;//模式串匹配计数
//当两串首位相等时进行i和j的累加
while (i < sLen && j < pLen && source.charAt(i) == pattern.charAt(j)) {
i++;
j++;
}
//如果j==pLen表示匹配成功,返回源串的起始位置k
if (j == pLen)
return k;
else {
//如果k+pLen小于sLen,说明后面还有字符串可进行匹配
if (k + pLen < sLen) {
/**
* 根据第1节中讲述的规则对k进行赋值
* 第1种情况,参与匹配最末尾字符的下一个字符没有出现在模式串中
* 那么根据map的初始化,map[source.charAt(k + pLen)]返回的结果是-1
* k=k+(pLen+1),似乎是精心设计好的,很棒
* 第2种情况,如果存在找到模式串中最右端匹配的字符
* map的功能便是这个,但是似乎发现这块并没有和上面一样+1,为什么?
* 很好理解,因为数组的下标是从0开始的,所以map[source.charAt(k + pLen)]返回的值本身就是小1的,很棒
*/
k += (pLen - map[source.charAt(k + pLen)]);
} else {
return -1;
}
}
}
return -1;
}
public static void main(String[] args) {
String source = "abcdefghijk";
String pattern = "hijk";
System.out.println(sundaySearch(source, pattern));
}
}
终于讲完了,其实sunday算法还是比较好理解的,熟悉一下,使用起来吧^_^
参考: