程序员必须会的基本算法4-KMP字符串查找算法(以暴力查找算法的缺点为背景)

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");
		}
	}	
}
发布了133 篇原创文章 · 获赞 37 · 访问量 4725

猜你喜欢

转载自blog.csdn.net/qq_43416157/article/details/104547286