字符串匹配算法
暴力匹配算法
每次都从父亲的当前位置i开始匹配,最坏情况下每一个i都要匹配,每次匹配子串的长度次,比如父串aaaaaab,子串b,时间复杂度为O(n^2)
//返回如果是子串的话,第一个起点的位置
public static int getIndexOf1(String father, String son) {
if (father==null||son==null||father.length() < son.length())
return -1;
int len1 = father.length();
int len2 = son.length();
char[] fa = father.toCharArray();
char[] so = son.toCharArray();
for (int i = 0; i < len1 - len2; i++) {
boolean isSame = true;
for (int j = 0; j < len2; j++) {
if (fa[i + j] != so[j]) {
isSame = false;
break;
}
}
if (isSame)
return i;
}
return -1;
}
KMP算法
前缀知识:最长前缀和最长后缀匹配
举个例子:比如给定字符串aabaabc。
对于第一个字符a来说,无前缀和后缀,所以它的next数组是-1,对于第二个字符a来说,前缀和后缀都是a,但是此时前缀和后缀都是前面串的全部了,不允许,所以它的next数组是0。为什么第一个没有是-1,第二个没有填0?第一个是前后缀都没有,没资格,所以-1;第二个是有,但是前后缀重合,不允许,所以下一个匹配的字符从0开始。
对于后面的字符来说,每次都是去它的前缀和后缀中找最长的,以最后一个字符c为例,它的最长前缀和最长后缀匹配的应该是aab,所以它的next数组处填3,其余以此类推。
得到next数组:
public static int[] getNextArray(char[] str) {
if (str.length == 1)
return new int[]{-1};
int[] next = new int[str.length];
next[0] = -1;
next[1] = 0;
int i = 2;
int cn = 0;
while (i < next.length) {
if (str[i - 1] == str[cn])
next[i++] = ++cn;
else if (cn > 0)
cn = next[cn];
else
next[i++] = 0;
}
return next;
}
解释一下代码逻辑:如果字符串长度为1,没有公共前后缀,直接返回-1即可。
根据前面的逻辑,直接填好next数组的前两个位置,然后i表示从当前位置开始,cn表示在i之前的字符串中,公共前后缀中前缀的下一个字符位置。
如果i-1处字符等于cn处的字符,也就是说当前失效匹配位置前的字符,等于上一个最长前缀的后一个字符,那么当前位置的最长前后缀长度应该是上一个的最长前缀的字符+1,如果不相等,那么就应该在上一个前缀中,循环该寻找过程,直到它回到0的起始位置,前面没东西了,那么就让此时的next数组处变为0。
KMP算法主函数位置:
如果两个字符串当前位置都匹配成功,x和y同时后移。否则,当前位置匹配失效,如果next[y]=-1说明第一个字符就失败了,那还匹配个集贸。直接让x后移一位,换人来;否则,让y去它的最长前缀位置,此时x不动,然后两个再去匹配,循环往复。
如果最后y到了子串的末尾的下一个位置,那么第一个位置就应该是x-y处,比如abbca,bbc,那么x=4,y=3,第一个位置就应该是x-y。
如果没有到达该位置,都跑完了,你还到达不了,返回-1。
//todo:两个信息,为什么从j位置出发,之前的位置为什么不用考虑;第二个问题:为什么从当前字符匹配,前面的0-j字符不用匹配了嘛
public static int getIndexOf2(String s1, String s2) {
if (s1 == null || s2 == null || s1.length() < s2.length() || s2.length() < 1)
return -1;
char[] str1 = s1.toCharArray();
char[] str2 = s2.toCharArray();
int[] next = getNextArray(str2);
int x = 0, y = 0;
while (x < str1.length && y < str2.length) {
if (str1[x] == str2[y]) {
++x;
++y;
} else if (next[y] == -1)
++x;
else
y = next[y];
}
return y == str2.length ? x - y : -1;
}
假设当前子串失效位置为y,为什么你可以直接回到前面的最长公共前缀位置处?为什么不用重新匹配?
理由:计算next数组时,计算的是最长前缀和后缀的匹配,所以必然相等。
假设当前父串失效位置为x,为什么可以不用回退?
理由:next数组已经给你计算过了,如果你回退,说明你前面的next数组计算错误,你活该死去。
对数器校验:
// 生成指定长度的随机字符串
public static String generateRandomString(int length) {
String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = new Random();
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
int randomIndex = random.nextInt(characters.length());
char randomChar = characters.charAt(randomIndex);
sb.append(randomChar);
}
return sb.toString();
}
public static void main(String[] args) {
int times = 1000000;
//跑times次,错一次就提前终止,并输出此时的字符串
for (int i = 0; i < times; i++) {
Random random = new Random();
int n = random.nextInt(20) + 10;
int m = random.nextInt(10) + 10;
//保证n一定不比m小
if (n < m) {
n ^= m;
m ^= n;
n ^= m;
}
String str = generateRandomString(n);
String str2 = generateRandomString(m);
if(getIndexOf1(str,str2)!=getIndexOf2(str,str2))
{
System.out.println("程序出错,检查算法");
System.out.println("第一个字符串"+str);
System.out.println("第二个字符串"+str2);
return ;
}
}
System.out.println("程序结束");
}