• KMP算法--Next数组详解与优化


    本篇文章直接跳过蛮力算法以及一些简单背景,着重讨论Next数组的意义以及其是如何工作的,并对如何求Next数组做详细记录。

    1.背景

    1.1 KMP算法的应用:KMP算法用来解决模式串匹配问题。

    1.2 为什么要用KMP算法:普通的蛮力算法时间复杂度为O(n*m),而KMP为O(n+m)。

    2.KMP算法思想

    2.1 KMP算法的思想:(称T为目标串,P为待查找字串)

    1. 目标串T的 i 指针不必回溯!
    2. 通过对待查找字串P进行分析得出当每次字符不匹配时P串应如何移动

    2.2 Next数组介绍:Next数组中存的是如果P[ j ] != T[ i ] 时,j 应该等于多少。即P串要向右移动多少位,此时 i 不变。Next[ j]代表前 j - 1 个字符的最大前缀和最大后缀相同的字符数,这样当P[ j ] != T[ i ]时,将j = Next[ j ] ,由于前缀与后缀相等,故此时前 j 个字符仍是匹配的。

    2.3 代码实例:

    int KMP(string a,string b){
    	BuildNext(b);//用来求Next数组
    	int n = a.length();
    	int m = b.length();
    	int i = 0,j = 0;
    	while(j < m && i < n){
    		if(j < 0 || a[i] == b[j])
    			i++,j++;
    		else j = Next[j];
    	}
    	return i-j;
    }

    3.Next数组的求法

    3.1 与KMP算法本身类似的思想:将P串自己与自己匹配。

    3.2 代码实例:

    void BuildNext(string P){
    	int m = P.length();
    	int t = Next[0] = -1;
    	int j = 0;
    	while(j < m-1){
    		if(t < 0 || P[j] == P[t]){
    			j++;
    			t++;
    			Next[j] = t;//待优化
    		}else t = Next[t];
    	}
    }

    3.3 解析:这里用到一个小技巧,令Next[0] = -1,叫做字符通配符,即可以和所有字符匹配,这样就可以在P没有前缀和T[i]匹配时P从头开始比较。

    4.优化

    4.1 对Next数组进行优化:当待匹配子串中有较多相同字符时,以上方法还是会进行很多次无谓的比较,比如T : 00100010与P :00010进行匹配,当知道T中的第三位1与P中的第三位0不匹配时,上述方法会将P串向右移动一位,结果仍是不匹配,但是我们已经知道了0和1不等,这样还有必要每次都向后移一位吗?为什么不直接移动3位?

    4.2 实现方法:我们在求前 i - 1 个元素前缀与后缀相等个数的时候,是不考虑第P[ i ]的,但是如果要是最大相等前缀的后一个字符和P[ i ]相等的话,不就表明即使你移动了之后,当前字符依然没变,也就依然不能匹配,所以要再接着移吗?

    4.3 代码实例:

    void BuildNext(string P){
    	int m = P.length();
    	int t = Next[0] = -1;
    	int j = 0;
    	while(j < m-1){
    		if(t < 0 || P[j] == P[t]){
    			j++;
    			t++;
    			Next[j] = P[j] != P[t]?t:Next[t];
    		}else t = Next[t];
    	}
    }

    5.完整代码
     

    #include<iostream>
    #include<cstdio>
    using namespace std;
    const int maxn = 1e5;
    int Next[maxn];
    void BuildNext(string P){
    	int m = P.length();
    	int t = Next[0] = -1;
    	int j = 0;
    	while(j < m-1){
    		if(t < 0 || P[j] == P[t]){
    			j++;
    			t++;
    			Next[j] = P[j] != P[t]?t:Next[t];
    		}else t = Next[t];
    	}
    }
    int KMP(string a,string b){
    	BuildNext(b);
    	int n = a.length();
    	int m = b.length();
    	int i = 0,j = 0;
    	while(j < m && i < n){
    		if(j < 0 || a[i] == b[j])
    			i++,j++;
    		else j = Next[j];
    	}
    	return i-j;
    }
    int main()
    {
    	string a,b;
    	getline(cin,a);
    	getline(cin,b);
    	int pos;
    	if(b.length() > a.length())	pos = -1;
    	else pos = KMP(a,b);
    	if(pos >= 0)	printf("%d
    ",pos);
    	else puts("NO");
    } 
  • 相关阅读:
    排列组合
    从$a_n=f(n)$的角度理解数列中的表达式$a_{n+1}=frac{k}{a_n}$
    洛必达法则的应用
    三角函数专题
    和差角公式的证明
    利用导数证明不等式
    常用数学模型整理
    教给学生知识的本源
    争鸣|两个易混概率题
    es6数组的复制
  • 原文地址:https://www.cnblogs.com/long98/p/10352173.html
Copyright © 2020-2023  润新知