KMP算法学习 & HDU 1686 Oulipo参考代码

KMP讲解推荐视频(来源于https://www.bilibili.com):

参考视频1

参考视频2


定义
真前缀:对于当前位置i,从0~k(0<=k<i)构成的子串;例如:abcd的真前缀有a,ab,abc
真后缀:对于当前位置i,从k~i(1<=k<=i)构成的子串;例如:abcd的真后缀有bcd,cd,d
最长公共真前、后缀:真前缀和真后缀相同,而且长度最长;例如:aaaa的最长公共真前、后缀为aaa,长度为3;abab的最长公共真前、后缀为ab,长度为2
next数组的含义:next[i]:存放i之前子串(0~i-1共i个字符构成)的最长公共真前、后缀的长度。


手算Next数组

对于模式串ababaabab,手算代码中getNext1中的Next数组(next1)和getNext2中的Next数组(next2)

tmp[i]:  对于下标i,存放0~ i的最长公共真前、后缀长度
next1[i]: next1[0]=-1,对于下标i,存放0~(i-1)的最长公共真前、后缀长度;若下标从1开始,则加1即可
next2[i]: next2[0]=-1,对于下标i,若next1[i]!=tmp[i]-1则为next1[i],否则为next2[ next1[i] ];若下标从1开始,则加1即可

下标 0 1 2 3 4 5 6 7 8
字符 a b a b a a b a b
tmp[i] 0 0 1 2 3 1 2 3 4
tmp[i]-1 -1 -1 0 1 2 0 1 2 3
next1[i] -1 0 0 1 2 3 1 2 3
next2[i] -1 0 -1 0 -1 3 0 -1 0

题目链接

题意:查找模式串t在主串s中出现的次数。


参考代码

//HDU 1686 查找模式串t在主串s中出现的次数,模式匹配,KMP算法
#include <iostream>
#include <string>
#include <vector>
using namespace std;

string s, t; //s主串,t模式串
vector <int> Next; //外部数组、变量不取名为next以免编译错,类似于max,min,count不作为外部变量名
int n, m; //s主串长度n,t模式串长度m


void getNext1()  // O(m),计算Next数组的方法
{
    int i=0, j=-1;
    Next[0]=-1;

    while(i<m)
    {   
        if(j==-1 || t[i]==t[j]) //从头比或相等则比较下去并记录最长公共真前、后缀长度
        {
            i++;
            j++;
            Next[i]=j; //i之前的最长公共真前、后缀长度为j,前缀下标0~j-1,后缀下标i-j~i-1
        } 
        else      
            j=Next[j]; //j回退到Next[j]的位置
    }
}

/*对于模式串:aaaab,以下若按getNext1中“Next[i]=j”的写法,Next[]={-1,0,1,2,3},则对于
主串:  aaabaaaab
模式串:aaaab
在i=3、j=3失配时,则还要i=3、j=2,i=3、j=1,i=3、j=0的比较
若按getNext2中“if (t[i]!=t[j]) Next[i]=j;else Next[i]=Next[j];”的写法,Next[]={-1,-1,-1,-1,3}
则在i=3、j=3失配时,直接进行i=3,j=-1的比较,即相当于直接进行i=4、j=0的比较
*/

void getNext2() //稍作改进的计算Next数组的方法
{
    int i=0, j=-1;
    Next[0]=-1;

    while(i<m)
    {   
        if(j==-1 || t[i]==t[j])
        {
            i++;
            j++;			
            if (t[i]!=t[j]) Next[i]=j;
			else Next[i]=Next[j]; 
        } 
        else
            j=Next[j];
    }
}

//KMP中,若s[i]==t[j],则继续比较,i++,j++,若j==m则匹配成功,首次成功时i-m为t在s中首次出现的位置;
//       若s[i]!=t[j],则j回退到next[j]的位置,相当于t右划使得下次j对应的字符与i对应的字符比较
int KMP(int &pos)  // O(n)
{    
    int i=0,j=0,cnt=0;      
    while(i<n)
    {  
        if(j==-1 || s[i]==t[j]) //j==-1相当于从t的开始(重新)比较,相等就比下去
        {
            i++;
            j++;	
			if(j==m)
			{
				if (pos==-1) pos=i-m; //记录t在s中首次出现的位置
				cnt++;       //计数器加1
				j=Next[j];   //把i位置的字符和j位置的字符对齐,便于下次比较
			}
        }
        else
            j=Next[j]; //j回退到Next[j]对应的下标
    }      
    return cnt;
}

void run()
{ 
    cin>>t>>s;  //输入模式串和主串
	n=s.size(); //主串长度
	m=t.size(); //模式串长度

	//简单起见(i++后next[i]=j不用判断i下标越界),最后一个字符后的位置也计算一下next值,
    Next.resize(t.size()+1);
	getNext1();
	//Next.clear();
    //getNext2();
	int pos=-1;
    cout << KMP(pos) <<endl;
	//if (pos==-1) cout<<"No Found\n";
	//else cout<<"Found, first occurence position=" <<pos <<endl;
}

int main()
{    
    int T;    
    cin>>T;
    while(T--) run();
    return 0;
} 

仅以此文纪念2018年暑期集训(2018-7-10~2018-8-4),愿我们在编程中学到东西,拓展思维,找到快乐!

PS:注释总是加了的好,脑筋还是动起来的好。

猜你喜欢

转载自blog.csdn.net/acmerhlj/article/details/81415278