BF算法
又叫朴素算法,男朋友算法,时间复杂度为O(mn)相比KMP算法比较简单,举个例子,对于给定的主字符串“ababbcabcdabcde”和子串“abcd”;我们用i和j来分别遍历两个字符串,比较两个i,j 对应字符串位置的元素是否相等,如果相等则i++,j++去比较下一个元素,如果i和j 对应位置的元素不相等,则j需要退回到子字符串的0号下标位置,而i需要退回到之前的位置+1,这个位置下标也就是i-j+1的位置,然后继续比较,相等i++,j++,不等j需要退回到子字符串的0号下标位置,i需要退回到i-j+1的位置,重复只到找到为止,找到返回i-j,找不到返回-1:
package com.xagy.chuan;
/*
* 朴素算法
*/
public class Bf {
/**
* BF算法
* @param str 字符串,主串
* @param sub 字符串,子串
* @param pos 从主串开始查找的位子
*/
public static int BF(String str,String sub,int pos) {
if(pos < 0 || pos > str.length()) {//判断pos合法性
return -1;
}
int i = pos;//开始查找
int j = 0;
//遍历主串和子串
while(i < str.length() && j<sub.length() || sub.length() > str.length()) {
//判断对应下标元素是否相同
if(str.charAt(i) == sub.charAt(j)) {
i++;
j++;
}else {
i = i-j+1;
j = 0;
}
}
if(j >= sub.length()) {//已经找到了子串在主串的位置
return i-j;
}
return -1;
}
public static void main(String[] args) {
String str = "ababbcabcdabcde";
String sub = "abcd";
System.out.println( BF(str,sub,0));
}
}
运行结果:
6
KMP算法
KMP算法的时间复杂度为O(m+n),所以它相比于BF算法更为高效,它的实现过程与BF算法唯一不一样的地方在于,KMP算法的主串i并不会回退,并且j也不会回退到0号位置:
这个时候主串i并不需要回退,并且j也不会回退到0号位置,而是回退到一个位置k,
至于子串中位置k的查找则需要用到next数组,
到了c的位置,按next数组返回,next数组为2,即返回到2号下标位置,也就是上面我们推得k位置,所以寻找k位置可以用next数组最方便,next数组的求法是一边从0号下标位置开始往后找,一边从当前j-i号位置开始从后往前找,找到最长有几个元素相等next数组就是几:
如:
而这种情况下,
这时候,对应的 next 数组:-1,0,0,0,1,2 next[2] 对应的就是 c.
假定主串 S 子串 P:
P0…PK-1 = Px…Pj-1 则有:P0…PK-1 = Pj-k…Pj-1
接下来又有一个问题:每一次不成功,肯定会有对应的一个 K 值。那么如何保存这些值?怎么求这些值?那这就是KMP 的精髓。在这里就会出现新的东西,那就是 next 数组;也就是用 next[j] = k;来表示,不同的 j 来对应一个 K 值,这个 K 就是你将来要移动的 j 要移动的位置。而 K 的值是这样求的:
1、规则:找到匹配成功部分的两个相等的真子串(不包含本身),一个以下标 0 开始,另一个以 j-1 下标结尾。
2、不管什么数据 next[0] = -1;next[1] = 0;在这里,我们以下标来开始,而说到的第几个第几个是从 1 开始;
-1 的理由:当主串为–”defrdes” 子串为:”abc” 一开始就匹配失败。
0 的理由:当子串在 1 号下标匹配,此时为 0;
- 得到 next 数组:如果我们能够通过 next[i]的值,通过一系列转换得到 next[i+1]得值,那么我们就能够实现这部分。
那该怎么做呢?首先假设:next[i] = k 成立,呢么,就有这个式子成立:P0…Pk-1 = Px…Pi-1;得到:P0…Pk-1 = Pi-k..Pi-1;
到这一步:我们再假设如果 Pk = Pi;我们可以得到 P0…Pk = Pi-k..Pi;那这个就是 next[i+1] = k+1;那么:Pk != Pi 呢?首先
在不相等的时候,看这个例子:abcababcabc
next 数组的优化,即如何得到 nextval 数组:
有如下串:aaaaaaaab,他的 next 数组是-1,0,1,2,3,4,5,6,7.而修正后的数组 nextval 是:
-1,-1,-1,-1,-1,-1,-1,-1,7。为什么出现修正后的数组,假设在 5 号处失败了,那退一步还是 a,还是相等,接着退还是 a。
代码:
package com.xagy.chuan;
//找到子串中的两个真子串,一个以0号下标开始,一个以j-1下标为止
public class Kmp {
/**
* KMP核心算法
* @param str
* @param sub
* @return
*/
public static int KMP(String str,String sub)
{
//先判断是否符合规范
if(str.length()<sub.length())
{
return -1;
}
//机获得Next数组
int [] next = getNext(sub);
//sub下标位置
int j=0;
//str的下标位置
int i=0;
//直到i,j超出下标
while(i<str.length()&&j<sub.length())
{
//如果str和sub的当前元素相等,则i和j同时移动
//j=-1为,如果在匹配第一个失败的时候,进行回退会回退到-1位置,数组会越界,所以直接向后移动
if(j==-1||str.charAt(i)==sub.charAt(j))
{
i++;
j++;
}
//否则j根据next数组进行回退,i不回退
else
{
j = next[j];
}
}
//如果j大于sub数组,则说明查找成功了,返回第一个找到的第一个下标
if(j>=sub.length())
{
return i-j;
}//如果没找到,返回-1
else
{
return -1;
}
}
/**
* 求Next数组
* @param sub 要匹配的字符串
* @return Next数组
*/
public static int[] getNext(String sub)
{ //创建一个数组
int[] next = new int[sub.length()];
//初始化
next[0] = -1;
next[1] = 0;
//当前要求next的位置
int i = 2;
//k位置
int k =0;
//循环直到最后一个节点
while(i<sub.length()-1)
{
//如果k=-1或者两元素相等
if(k==-1||sub.charAt(k)==sub.charAt(i-1))
{
//当前的next大小等于前一个next+1
next[i++] = ++k;
}//如果不相等,进行回溯,寻找可以匹配的最小子串
else
{
k = next[k];
}
}
//返回next数组
return next;
}
public static void main(String[] args) {
String str = "abcabbcabcaabdab";
String sub = "abbcabca";
System.out.println(KMP(str,sub));
}
}
运行结果 :
3