现有字符串匹配算法有不少,如简单暴力的朴素算法(暴力匹配算法)、KMP算法、BM算法以及Sunday算法等,在这里仅介绍前两种算法。
// 定义接口
interface StringMatcher {
/**
* 从原字符串中查找模式字符串的位置,如果模式字符串存在,则返回模式字符串第一次出现的位置,否则返回-1
*
* @param source
* 原字符串
* @param pattern
* 模式字符串
* @return if substring exists, return the first occurrence of pattern
* substring, return -1 if not.
*/
int indexOf(String source, String pattern);
}
/**
* 暴力匹配
*
* 时间复杂度: O(m*n), m = pattern.length, n = source.length
*/
class ViolentStringMatcher implements StringMatcher {
@Override
public int indexOf(String source, String pattern) {
int i = 0, j = 0;
int sLen = source.length(), pLen = pattern.length();
char[] src = source.toCharArray();
char[] ptn = pattern.toCharArray();
while (i < sLen && j < pLen) {
if (src[i] == ptn[j]) {
// 如果当前字符匹配成功,则将两者各自增1,继续比较后面的字符
i++;
j++;
} else {
// 如果当前字符匹配不成功,则i回溯到此次匹配最开始的位置+1处,也就是i = i - j + 1
// (因为i,j是同步增长的), j = 0;
i = i - j + 1;
j = 0;
}
}
// 匹配成功,则返回模式字符串在原字符串中首次出现的位置;否则返回-1
if (j == pLen)
return i - j;
else
return -1;
}
}
next[0] = -1。
如果已知next[j] = k,如何求出next[j+1]呢?具体算法如下:
如果p[j] = p[k], 则next[j+1] = next[k] + 1;
如果p[j] != p[k], 则令k=next[k],如果此时p[j]==p[k],则next[j+1]=k+1,如果不相等,则继续递归前缀索引,令 k=next[k],继续判断,直至k=-1(即k=next[0])或者p[j]=p[k]为止
详细的介绍及分析还请移步从头到尾彻底理解KMP,在下语拙
Java实现:
/**
* KMP模式匹配
* @author Tianma
*
*/
public class KMPStringMatcher {
public KMPStringMatcher(String source, String pattern) {
this.source = source;
this.pattern = pattern;
}
private String source,pattern;
/**
* 获取KMP算法中pattern字符串对应的next数组
*
* @param p
* 模式字符串对应的字符数组
* @return
*/
protected int[] getNext(char[] p) {
// 已知next[j] = k,利用递归的思想求出next[j+1]的值
// 如果已知next[j] = k,如何求出next[j+1]呢?具体算法如下:
// 1. 如果p[j] = p[k], 则next[j+1] = next[k] + 1;
// 2. 如果p[j] != p[k], 则令k=next[k],如果此时p[j]==p[k],则next[j+1]=k+1,
// 如果不相等,则继续递归前缀索引,令 k=next[k],继续判断,直至k=-1(即k=next[0])或者p[j]=p[k]为止
int pLen = p.length;
int[] next = new int[pLen];
int k = -1;
int j = 0;
next[0] = -1; // next数组中next[0]为-1
while (j < pLen - 1) {
if (k == -1 || p[j] == p[k]) {
k++;
j++;
next[j] = k;
} else {
k = next[k];
}
}
return next;
}
public int indexOf() {
int i = 0, j = 0;
char[] src = source.toCharArray();
char[] ptn = pattern.toCharArray();
int sLen = src.length;
int pLen = ptn.length;
int[] next = getNext(ptn);
while (i < sLen && j < pLen) {
// 如果j = -1,或者当前字符匹配成功(src[i] = ptn[j]),都让i++,j++
if (j == -1 || src[i] == ptn[j]) {
i++;
j++;
} else {
// 如果j!=-1且当前字符匹配失败,则令i不变,j=next[j],即让pattern模式串右移j-next[j]个单位
j = next[j];
}
}
if (j == pLen)
return i - j;
return -1;
}
}
/**
* 优化的KMP算法(对next数组的获取进行优化)
*
* @author Tianma
*
*/
class OptimizedKMPStringMatcher extends KMPStringMatcher {
@Override
protected int[] getNext(char[] p) {
// 已知next[j] = k,利用递归的思想求出next[j+1]的值
// 如果已知next[j] = k,如何求出next[j+1]呢?具体算法如下:
// 1. 如果p[j] = p[k], 则next[j+1] = next[k] + 1;
// 2. 如果p[j] != p[k], 则令k=next[k],如果此时p[j]==p[k],则next[j+1]=k+1,
// 如果不相等,则继续递归前缀索引,令 k=next[k],继续判断,直至k=-1(即k=next[0])或者p[j]=p[k]为止
int pLen = p.length;
int[] next = new int[pLen];
int k = -1;
int j = 0;
next[0] = -1; // next数组中next[0]为-1
while (j < pLen - 1) {
if (k == -1 || p[j] == p[k]) {
k++;
j++;
// 修改next数组求法
if (p[j] != p[k]) {
next[j] = k;// KMPStringMatcher中只有这一行
} else {
// 不能出现p[j] = p[next[j]],所以如果出现这种情况则继续递归,如 k = next[k],
// k = next[[next[k]]
next[j] = next[k];
}
} else {
k = next[k];
}
}
return next;
}
}
// String.indexOf(String str); 最终会调用该方法
/**
* Code shared by String and StringBuffer to do searches. The
* source is the character array being searched, and the target
* is the string being searched for.
*
* @param source the characters being searched.(源字符数组)
* @param sourceOffset offset of the source string.(源字符数组偏移量)
* @param sourceCount count of the source string.(源字符数组长度)
* @param target the characters being searched for.(待搜索的模式字符数组)
* @param targetOffset offset of the target string.(模式字符数组偏移量)
* @param targetCount count of the target string.(模式数组长度)
* @param fromIndex the index to begin searching from.(从原字符数组的哪个下标开始查询)
*/
static int indexOf(char[] source, int sourceOffset, int sourceCount,
char[] target, int targetOffset, int targetCount,
int fromIndex) {
if (fromIndex >= sourceCount) {
return (targetCount == 0 ? sourceCount : -1);
}
if (fromIndex < 0) {
fromIndex = 0;
}
if (targetCount == 0) {
return fromIndex;
}
char first = target[targetOffset];
int max = sourceOffset + (sourceCount - targetCount);
for (int i = sourceOffset + fromIndex; i <= max; i++) {
/* Look for first character. */
// 找到第一个匹配的字符的位置
if (source[i] != first) {
while (++i <= max && source[i] != first);
}
/* Found first character, now look at the rest of v2 *
if (i <= max) {
// 找到了第一个匹配的字符,看余下的是否完全匹配
int j = i + 1;
int end = j + targetCount - 1;
for (int k = targetOffset + 1; j < end && source[j]
== target[k]; j++, k++);
if (j == end) {
/* Found whole string. */
return i - sourceOffset;
}
// 如果不完全匹配,因为外层for循环中有i++,即i+1继续匹配
// 故而该方法本质上就是字符串匹配的朴素算法
}
}
return -1;
}
通过对代码片段的注释和分析可以看出,Java源码中的String.indexOf(String str); 内部所使用的算法其实就是字符串匹配的朴素算法…
参考链接:https://www.jianshu.com/p/e2bd1ee482c3