package basic;
import org.junit.Test;
public class KMP
{
/*
* KMP算法:它是解决一个子字符串是否在文本字符串里面出现过的问题,
* 如果出现过就返回最先出现的位置,否则返回-1,也叫字符串查找算法,
* 在了解KMP算法之前先了解一下暴力匹配算法,详细的转到最下面阅读
* 下面说一下KMP算法
* 文本字符串str1="JABCDABJABCDABCDABFE "
* 子字符串 str2="ABCDABF"
* 这里从文本串第一个开始匹配,一直匹配到和子串第一个相等,变成
*
* 文本字符串str1="ABCDABJABCDABCDABFE "
* 子字符串 str2="ABCDABF"
* 因为第一个J没有匹配到,就会过了,匹配到有相等的,一直到匹配到文本串的J,
* 发现又不相等了,如果是暴力匹配的话,就会回溯到和文本串的第三个字母B进行
* 匹配,很明显,从第二个字母开始的ABCDAB都是匹配过的,这里又开始重复的匹配,
* 明显不合理,这时KPM算法就提出了一个部分匹配值的说法,下面介绍一下它:
*
* 部分匹配值:
* 一个字符串我们想将它分成前缀和后缀,而部分匹配值就是前缀和后缀的最长的公告有的元素的长度
* 下面以子串"ABCDABF"进行例子说明:
* 子串每一个字母都有它的匹配值,现在一个一个地解释
* "A":前缀是空,后缀是空,公共有的元素是0,长度是0
* "AB":前[A],后[B],也是0
* "ABC":前[A,AB],后[C,BC],也是0
* "ABCD:前[A,AB,ABC],后[D,CD,BCD],是0
* "ABCDA":前[A,AB,ABC,ABCD],后[A,DA,CDA,BCDA],有个A是公共的,长度是1
* 下面同理得 [A B C D A B C]得
* 部分匹配值是[0,0,0,0,1,2,3]
* 可以看出部分匹配值得实质就是看子串的前面和后面的相同度,到后面的A就和前面一个A相同
* 后面A的匹配值就是1,后面的B加上前一个A,就AB和前面AB相同,B的匹配值是2,
* 这样的想法是当子串匹配到ABCDAB时还是相等,但是匹配ABCDABF时,F不相等,这时就不是
* 回溯了,而是将文本串的位置匹配位置向后移动(子串已经匹配的长度(现在是6)-最后匹配字母的部分匹配值(现在是2))
* 这样就直接跳四个位置(这里的例子是四个)进行匹配,继续匹配子串AB的后面是否符合,
* 是子串的对应的扫描器j-4,文本串的i是没有变的
*
* 了解完部分匹配值,继续上面的例子:
* 发现子串的F不匹配,前面ABCDAB是匹配的,最后一个匹配的字母B对应的部分匹配值是2
* 所以子串匹配位置向后移动6-2=4位,子串从AB后面开始匹配,变成
*
* 文本字符串str1="ABJABCDABCDABFE "
* 子字符串 str2="ABCDABF"
* 又因为C不匹配,向后移动(2-0=2)位,变成
*
* 文本字符串str1="JABCDABCDABFE "
* 子字符串 str2="ABCDABF"
* 继续后移一位:
* 文本字符串str1="ABCDABCDABFE "
* 子字符串 str2="ABCDABF"
* 经过匹配又要向后移动四位得到结果
*/
public static void main(String[] args)
{
String str1 = "0123456789";
String str2 = "123";
System.out.println(KMPAlgorithm(str1, str2));
}
public static int KMPAlgorithm(String str1,String str2)
{
int[] array = KMPArray(str2);
int j=0;
for(int i=0;i<str1.length();i++)
{
while(j>0&&str1.charAt(i)!=str2.charAt(j))
{
j=array[j-1];
}
if(str1.charAt(i)==str2.charAt(j))
{
j++;
}
if(j==str2.length())
{
return i-(j-1);
}
}
return -1;
}
/**
* 传入子串,返回对应的部分匹配值
* @param str2
* @return
*/
public static int[] KMPArray(String str2)
{
int[] arr=new int[str2.length()];
arr[0]=0;
int j=0;
for(int i=1;i<str2.length();i++)
{
/*
* KMP算法的核心就是这个while循环了,如果j还不是0,就是子串还是有一部分是匹配的
* 就是还没回到最开始的地方,如果下一个匹配的不成功,子串就一直进行跳
*/
while(j>0&&str2.charAt(i)!=str2.charAt(j))
{
j=arr[j-1];
}
if(str2.charAt(i)==str2.charAt(j))
{
j++;
}
arr[i]=j;
}
return arr;
}
/*
* 暴力匹配字符串算法
* 就是对于文本字符串进行扫描,如果匹配到和子字符串相等的位置就继续扫描下一个位置
* 对于文本字符串有i扫描,子字符串有j扫描,如果匹配成功就i++和j++
* 如果匹配不成功就进行回溯到下一个位置,就是if(str1[j]!=str2[j])
* then(i=i-(j-1),j=0;
* 这个算法很简单,缺点很明显,就是很多回溯,如果匹配不成功,之前的匹配都是浪费了
*/
@Test
public void Match()
{
String str1 = "0123456789";
String str2 = "123";
int i=0,j=0;
while(i<str1.length()&&j<str2.length())
{
if(str1.charAt(i)==str2.charAt(j))
{
i++;j++;
}
else
{
i=i-(j-1);
j=0;
}
}
if(j==str2.length())
{
System.out.println(i-j);
}
else
{
System.out.println("-1");
}
}
}