一、简介
单模式串匹配算法:是在一个模式串和一个主串之间进行匹配,也就是说在一个主串中查找另一个模式串
- BF:主串和模式串都不太长,时间复杂度为O(m*n)
- BM:模式串最好不要太长,预处理较重,实现较复杂,需要更多额外空间
- KMP:适合所有场景,整体实现起来也比BM简单,只需要一个next数组,但理解起来较难且统计分析下比BM慢
- sundy:算是对BM算法的改造,实现起来较简单
二、BF(Brute-Force-Match)
package StringMatch;
/*
字符串匹配:暴力匹配算法
*/
public class VoliceMatch {
public static int volicematch(String a,String b){
char[] s1 = a.toCharArray();
char[] s2 = b.toCharArray();
int m = s1.length;
int n = s2.length;
for (int i = 0; i <= m - n;i++){
int k = 0;
for (int j = 0;j < n;j++){
if (s1[i+j] != s2[j]){
break;
}else{
k++;
}
}
if (k == n){
return i;
}
}
return -1;
}
public static void main(String[] args) {
System.out.println(volicematch("hellllo","lo"));
}
}
三、BM(Boyer-Moore)
package StringMatch;
/*
BM算法:
思路:坏字符和好后缀原则
eg:第一次匹配
主串a:abcebmabcnbabceba,长度为m
模式串b:abaeba,长度为n
坏字符原则:从后往前匹配,把匹配不上的对应的主串的字符称为坏字符,若坏字符为倒数第一位,则可以往右直接滑动n位,若坏字符不确定,为了保证不移动过多而错过匹配,可以把坏字符对应的模式串下标记为s,
把坏字符在模式串中的位置记为x,若坏字符在模式串中不存在,x为-1,若坏字符在模式串中有多个,取最后一个下标,往右滑动s-x位,第一次匹配时,坏字符为m,s为5,m在模式串中找不到,记为-1,往右移动6位.
但仅仅靠坏字符匹配是不行的,比如主串是baabaaa,模式串为aaab,第一次匹配时,坏字符为b,s为0,x为b在模式串中位置,为3,往右移动了-3位,显然是不行的,为此引入的好后缀原则
eg:第二次匹配
主串a:abcebmabcnbabaeba,长度为m
模式串b: abaeba,长度为n
好后缀原则:为了弥补坏字符可能出现往右偏移为负数的情况,如上述eg中第二次匹配时,坏字符为n,但是坏字符后的ba是能够匹配上的,我们称之为好后缀,假如好后缀在模式串中能找到另一个,开始下标记为x,往右偏移
s-x+1位 = 3 - 1 + 1 = 3位,相当于如下所示,把另一个ba与好后缀ba对应上
主串a:abcebmabcnbabaeba,长度为m
模式串b: abaeba,长度为n
若好后缀在模式串中找不到另一个呢?如下所示,这时候判断好后缀的后缀子串与模式串的前缀子串能否匹配上,如好后缀为ba,后缀子串为a,a正好能跟模式串的第一位匹配上,这时往右移动j+2=5位
主串a:abcebmabcnbabmeba,长度为m
模式串b: abmeba,长度为n
上图所示例子好后缀只有两位,所以好后缀的后缀子串只能有1位,但好后缀的后缀子串有多位呢?此时还能移动j+2位吗?,如下所示,好后缀为ceba,且模式串中找不到ceba,此时eba的后缀子串为eba,ba,a,此时只有ba能与模式串的前缀子串匹配上,应该往
右偏移j+2+1位,可以这么理解若好后缀的后缀子串只有1位能与模式串前缀子串匹配上,直接偏移j+2,好后缀的后缀子串增加一位,j+2相应加1
主串a:abcebmabcebabaeba,长度为m
模式串b: baceba,长度为n
用坏字符和好后缀都会得到一个往右的偏移量,比较哪个偏移量大即可
*/
public class BmMatch_zhu {
private static final int SIZE = 256;
public static int bmmatch_zhu(String str1,int m,String str2,int n){
char[] a = str1.toCharArray();
char[] b = str2.toCharArray();
int[] bc = new int[SIZE];
geneteBc(b,n,bc);
int[] hz = new int[n];
boolean[] qz = new boolean[n];
genetehzqz(b,n,hz,qz);
int i = 0;
while (i <= m - n){
int j;
for (j = n - 1;j >= 0;j--){
if (a[i+j] != b[j]){
break;
}
}
if (j == -1){
return i;
}
int x = j - bc[(int)a[i+j]];
int y = 0;
if (j < n - 1){
y = move(j,n,hz,qz);
}
i = i + Math.max(x,y);
}
return -1;
}
private static void geneteBc(char[] b,int n,int[] bc){
for (int i = 0; i < SIZE;i++){
bc[i] = -1;
}
for (int i = 0; i < n; i++){
int ascii = (int)b[i];
bc[ascii] = i;
}
}
private static void genetehzqz(char[] b,int n,int[] hz,boolean[] qz){
for (int i = 0; i < n; i++){
hz[i] = -1;
qz[i] = false;
}
for (int i = 0; i < n - 1;i++){
int j = i;
int k = 0;
while (j >= 0 && b[j] == b[n-1-k]){
j--;
k++;
hz[k] = j + 1;
}
if (j == -1){
qz[k] = true;
}
}
}
private static int move(int j,int n,int[] hz,boolean[] qz){
int k = n - j - 1;
if (hz[k] != -1){
return j - hz[k] + 1;
}
for (int r = j + 2; r <= n - 1;r++){
if (qz[n - r] == true){
return r;
}
}
return n;
}
public static void main(String[] args) {
System.out.println(bmmatch_zhu("hello",5,"lo",2));
}
}
四、KMP(Knuth-Morris-Pratt)
package StringMatch;
/*
思路:好前缀原则
不同于bm算法的是,kmp算法是从前往后比较,这样找到坏字符后比较的就是好前缀,如
主串a:ababaeabac,长度为m
模式串b:ababacd,长度为n
上述eg中坏字符为主串中的e,e前面的字符就称为好前缀,我们要在好前缀中找到最长可匹配前缀子串,如ababa的最长可匹配前缀子串为aba,找到最长可匹配前缀子串的最后一个字符下标位置,即2,比较b[2]和坏字符位置即可
主串a:ababaeabac,长度为m
模式串b: ababacd,长度为n
*/
public class KmpMatch_zhu {
public static int kmpmatch_zhu(String str1,int m,String str2,int n){
char[] a = str1.toCharArray();
char[] b = str2.toCharArray();
int[] next = getNext(b,n);
int j = 0;
for (int i = 0; i < m;i++){
while (j > 0 && a[i] != b[j]){
j = next[j-1]+1;
}
if (a[i] == b[j]){
j++;
}
if (j == n){
return i - n + 1;
}
}
return -1;
}
public static int[] getNext(char[] b,int n){
int[] next = new int[n];
next[0] = -1;
int k = -1;
for (int i = 1; i < n; i++){
while(k != -1 && b[k+1] != b[i]){
k = next[k];
}
if (b[k+1] == b[i]){
k++;
}
next[i] = k;
}
return next;
}
public static void main(String[] args) {
String str1 = "helllo";
String str2 = "lo";
System.out.println(kmpmatch_zhu(str1,str1.length(),str2,str2.length()));
}
}
五、Sundy
package StringMatch;
/*
思路:Sundy是对BM算法的简化
BM算法是模式串从后往前匹配,Sundy算法和Kmp算法一样,从前往后匹配,当遇到不匹配的时候
eg:
主串: substringsearching,长度为m
模式串:search,长度为n
比如说第二个字符就不匹配,找出主串中参与匹配的末尾字符的下一个字符,这里是i,判断i在模式串中存不存在,若不存在,直接把模式串往右偏移n+1位,如下所示
substringsearching
search
若存在,如上所示,第二次匹配不上的是n,末尾的下一次字符是c,c在模式串是存在的,找到c在模式串对应的最后一个下标,即3,模式串往右偏移n-3 = 3位,如下所示,就匹配上了
substringsearching
search
因为sundy算法没有好后缀原则,当遇到某种极端情况下是比较慢的,如
baaaaaaa
aaaaa
这里第一次b是不匹配的,末尾的下一个字符为a,找到a在模式串的最后一个下标,即4,模式串往右偏移n-4 = 1位,若都是这种情况,可能每次都移动一位,时间复杂度就退化到和暴力匹配一样O(m*n)
*/
public class SundyMatch {
private static final int SIZE = 256;
public static int sundymathc(String str1,int m,String str2, int n){
char[] a = str1.toCharArray();
char[] b = str2.toCharArray();
int[] move = getMove(b,n);
int i = 0;
while(i <= m - n){
int j = 0;
while (a[i+j] == b[j]){
j++;
if (j == n){
return i;
}
}
i += move[(int)a[i+n]];
}
return -1;
}
private static int[] getMove(char[] b,int n){
int[] move = new int[SIZE];
//初始化
for (int i = 0; i < SIZE; i++){
move[i] = n + 1;
}
for (int i = 0; i < n; i++){
int ascii = (int)b[i];
move[ascii] = n - i;
}
return move;
}
public static void main(String[] args) {
String a = "helllllllo";
String b = "lo";
System.out.println(sundymathc(a,a.length(),b,b.length()));
}
}