[算法学习]字符串匹配

        一段正在被编辑的文本构成一个文件,而所要搜寻的模式是用户正在输入的特定的关键词,有效解决这个问题的算法叫做字符串匹配算法。假设给定文本是长度为n的数组T,而模式是一个长度为m的数组P。算法导论第32章,字符串匹配问题。本章一共给出了四个算法,分别是朴素算法,Rabin-Karp算法,有限自动机算法和kmp算法。


一、朴素算法

        朴素算法就是直接的暴力破解,两层循环,外层n-m+1,内层m,所以时间复杂度为O((n-m+1)*m)。代码如下

<span style="font-size:18px;">	public static List<Integer> naiveStringMatcher(String T, String P){
		List<Integer> re=new ArrayList<Integer>();
		int n=T.length();
		int m=P.length();
		for(int i=0; i<=n-m; i++){
			int j;
			for(j=0; j<m; j++){
				if(P.charAt(j)!=T.charAt(i+j)){
					break;
				}
			}
			if(j==m){
				re.add(i);
			}
		}
		return re;
	}</span>

        程序也比较简单,不多介绍了。


二、Rabin-Karp算法

        个人理解Rabin-Karp算法是朴素算法的改进,它在最坏情况下运行情况下与朴素算法相同即O((n-m+1)*m),从代码也可以看出,两个算法的外层是完全一样的,都是n-m+1,优化之处在于内层,朴素算法是从0到m逐个比较,所以复杂度是m,但是Rabin-Karp算法通过一个哈希函数,将m长度的字符串进行映射,通过O(1)时间就能求出,所以算法的过程就是预处理时求出P的哈希值p,然后在外层循环中逐个求内层m个字符串的哈希值t,比较二者是否相同,如果哈希值相同,再进一步判断两个字符串是否完全相同,所以只要哈希函数设计的好,算法的运行时间可以达到O(n+m)。

	public static List<Integer> rabinKarpMatcher(String T, String P){
		List<Integer> re=new ArrayList<Integer>();
		int n=T.length(), m=P.length();
		int q=13;//直接取素数13
		int d=10, h=1;//d直接去10
		int p=0, t=0;
		
		for(int i=0; i<m-1; i++){//根据P的长度求出h,即具有m数位的文本窗口的高位数位上的数字“1”的值
			h=(h*d)%q;
		}
		for(int i=0; i<m; i++){//预处理,初始化求出p和t0的值
			p=(d*p+P.charAt(i)-'0')%q;
			t=(d*t+T.charAt(i)-'0')%q;
		}
		for(int i=0; i<=n-m; i++){//无论何时执行,都有根据t(i)求t(i+1)
			if(p==t){//如果p与t(i)相等,说明取模相等,但有可能是伪命中点
				int j;
				for(j=0; j<m; j++){//进一步判断
					if(P.charAt(j)!=T.charAt(i+j)){
						break;
					}
				}
				if(j==m){//如果完全一样,则加入答案中
					re.add(i);
				}
			}
			if(i<(n-m)){//根据t(i)求t(i+1)
				t=(d*(t-(T.charAt(i)-'0')*h)+T.charAt(i+m)-'0')%q;
				if(t<0){
					t+=q;
				}
			}
		}
		return re;
	}

三、有限自动机


四、KMP算法

        KMP应该是字符串匹配问题最常见的算法了,反正我是最先接触到这个算法,后来才在算法导论上看到有专门的一章介绍。这个算法的匹配时间为O(n),无需像有限自动机一样计算转移矩阵,只用到一个辅助数组Pi,它在O(m)时间内根据P计算出来。

        其实匹配函数和预处理函数的大体相同,都是一个字符串针对模式P的匹配,只不过预处理过程是模式P本身针对自己的匹配。所以程序虽然长,但是只要理解了过程,也可以写出来,需要注意的就是与书上不同点在于,书上的数组都是从1开始,而程序中都是从0开始的,所以程序中需要作出调整,我在自己实现的时候把k的取值从-1开始,然后在后面再相印的加一。

	//预处理,求出Pi数组 O(m)
	private static int[] computePrefix(String P){
		int m=P.length();
		int[] Pi=new int[m];
		
		Pi[0]=0;
		int k=-1;
		for(int i=1; i<m; i++){
			while(k>=0&&P.charAt(k+1)!=P.charAt(i)){
				k=Pi[k]-1;
			}
			if(P.charAt(k+1)==P.charAt(i)){
				k++;
			}
			Pi[i]=k+1;
		}
		return Pi;
	}
	
	//KMP算法 匹配过程 O(n)
	public static List<Integer> kmpMatcher(String T, String P){
		List<Integer> re=new ArrayList<Integer>();
		int n=T.length();
		int m=P.length();
		int[] Pi=computePrefix(P);
		int k=-1;
		for(int i=0; i<n; i++){
			while(k>=0&&P.charAt(k+1)!=T.charAt(i)){
				k=Pi[k]-1;
			}
			if(P.charAt(k+1)==T.charAt(i)){
				k++;
			}
			if(k==m-1){
				re.add(i-m+1);
				k=Pi[k]+1;
			}
		}
		return re;
	}	

五、总结

        以上是根据算法导论书上的内容结合自己的理解,代码全部是自己写的。


你可能感兴趣的:(算法,String)