Loading

模式匹配

KMP

KMP实现

//求一个字符串在另一个字符串出现的第一个位置       

# include <stdio.h>
# include <string.h>
# include <stdlib.h>

int* NextImprove(char *P)
{
	int m=strlen(P),j=0;	//主串指针
	int* N=(int*)malloc(m*sizeof(int));  //Next表
	int t=N[0]=-1;		//P[-1]为通配符

	while(j<m-1)
	{
		if(t<0 || P[j]==P[t])
		{
			j++;
			t++;
			N[j]=P[j]!=P[t]?t:N[t];				//改进版
		}
		else
			t=N[t];
	}
	return N;
}


//next构建思路(递推结合画图):next[j+1]<=next[j]+1  
//当且仅当P[j]=P[next[j]]是取等号,即j对应的字符与next[j]对应的字符相同
//对next[j]的理解:即是在P[0,j)中,最大自匹配的真前缀和真后缀的长度
//next[j]即是最大自匹配的真前缀和真后缀的长度,也是比对失败时应该跳转到的位置
int* BuildNext(char *P)					//未改进版
{
	int m=strlen(P),j=0;	//主串指针
	int* N=(int*)malloc(m*sizeof(int));  //Next表
	int t=N[0]=-1;		//P[-1]为通配符

	while(j<m-1)				//因为根据m-1就可以算出m的值,所以只循环到m-1
	{
		if(t<0 || P[j]==P[t])
			N[++j]=++t;
		else
			t=N[t];
	}
	return N;
}

int match(char* P,char* T)	//KMP主算法	P为模式串,T为为文本
{
	int* Next=BuildNext(P);
	int n=strlen(T),i=0;
	int m=strlen(P),j=0;
	 while(i<n && j<m)
	 {
		if(j<0 || P[j]==T[i])
		{
			i++;
			j++;
		}
		else
	 		j=Next[j];
	 }
	 return i-j;
}

int main()  
{
	char P[100]="myj";
	char T[100]="hahamyj";
	int len1=strlen(P);
	int len2=strlen(T);
	int Position=match(P,T);
	if(Position<=len2-len1)			//len1 和len2 位字符串的个数,如果返回的值超过len1-len2说明P不在T中
		printf("%d\\n",Position);
	else
		printf("未出现\\n");
}

KMP打表

/*
求循环周期
打表题:用快速幂求出前几百的结果,找规律。发现在42时结果是循环的。
        把前42的结果放入数组
知识点:快速求幂
*/

/*

KMP求最小循环节:
若知道字符串为循环字符串
对于next数组中的 i, 符合 i % ( i - next[i] ) == 0 && next[i] != 0 , 则说明字符串循环
而且循环节长度为:   i - next[i]
循环次数为:       i / ( i - next[i] )


若i % ( i - next[i] )!=0 说明需要再添加几个字母才能补全
需要补的个数是循环节长度-余数

参考:
<https://www.cnblogs.com/jackge/archive/2013/01/05/2846006.html>
讲解:<https://segmentfault.com/a/1190000008575379>


题目:poj 3461、poj 2752、poj 2406、poj1961
代码参考:<https://blog.csdn.net/guhaiteng/article/details/52108690>


补:扩展KMP

*/


# include <stdio.h>
# include <math.h>
#include <stdlib.h>
#include <string.h>

int tmp;



int BuildNext(char *P)         //未改进版
{
  int m=strlen(P),j=0;  //主串指针
  int* N=(int*)malloc(m*sizeof(int));  //Next表
  int t=N[0]=-1;    //P[-1]为通配符

  while(j<m)        //因为根据m-1就可以算出m的值,所以只循环到m-1
  {
    if(t<0 || P[j]==P[t])
    {
      N[++j]=++t;
      if(j%(j-N[j])==0 && N[j] != 0)  //求循环最小节条件,比如12341234123 求得循环点为4.因为是循环点,所以循环次数至少为2才能求的出来
      {
        tmp=j-N[j];
        return tmp;
      }
    }
    else
      t=N[t];
  }
  return -1;
}

long long pow_mod(long long a,long long i,long long n)
{
    if(i==0) return 1%n;
    int temp=pow_mod(a,i>>1,n);
        temp=temp*temp%n;
    if(i&1) temp=(long long)temp*a%n;
    return temp;
}

int main()  
{
    long long i,j;
    long long sum=0;
    char A[3000];
    A[0]='0';               //注意,要把今天也算进去
    int m;

    for (i=1; i < 300; i++)
    {
        A[i]=pow_mod(i,i,7)+'0';
    }
    BuildNext(A);
    printf("%d\\n",tmp);
    return 0;
}

KMP匹配串次数

//一个字符串在另外一个字符串出现的次数
# include <stdio.h>
# include <string.h>
# include <stdlib.h>

int* BuildNext(char *P)					//未改进版
{
	int m=strlen(P),j=0;	//主串指针
	int* N=(int*)malloc(m*sizeof(int));  //Next表
	int t=N[0]=-1;		//P[-1]为通配符
    
	while(j<m)					//把m-1改为了m,是为了算N[m],算出这个串整体的最大公共前缀和后缀。
								//假想法:假想P[m]添加一个跟任何字符都匹配失败的字符,然后把添加后的P正常使用KMP就好。只是,在j==m时,sum++
	{
		if(t<0 || P[j]==P[t])
			N[++j]=++t;
		else
			t=N[t];
	}
	return N;
}

void match(char* P,char* T)	
{
	int sum=0;
	int* Next=BuildNext(P);
    
	int n=strlen(T),i=0;
	int m=strlen(P),j=0;     
	 while(i<n)
	 {
         
		if(j<0 || P[j]==T[i])
		{
			i++;
			j++;
            
		}
		else
	 		j=Next[j];
        if(j>=m)		//当j==m说明字符串与母串匹配成功。假想P[m]有一个跟任何字符都匹配失败的字符,
        				//于是把模式串进行与普通失败时同样的移动,也就是j=Next[j],
	 	{
            sum++;
	 		j=Next[j];
	 	}
         
	 }
    printf("%d\\n",sum);
	 return;
}

int main()  
{
	char P[10010];
	char T[1000010];
	int N;
	scanf("%d",&N);
	while(N--)
	{
		scanf("%s",P);
        scanf("%s",T);
		int len1=strlen(P);
		int len2=strlen(T);
		match(P,T);
	}
}

字符串所有公共前缀后缀

/*
POJ 2752
给一个字符串,求它的所有公共前缀后缀的长度(其中包括了最大公共前缀后缀)
*/

# include <stdio.h>
# include <string.h>
# include <stdlib.h>

int* BuildNext(char *P)					//未改进版
{
	int m=strlen(P),j=0;	//主串指针
	int* N=(int*)malloc(m*sizeof(int));  //Next表
	int t=N[0]=-1;		//P[-1]为通配符
    
	while(j<m)					//把m-1改为了m,是为了算N[m],算出这个串整体的最大公共前缀和后缀。
								//假想法:假想P[m]添加一个跟任何字符都匹配失败的字符,然后把添加后的P正常使用KMP就好。只是,在j==m时,sum++
	{
		if(t<0 || P[j]==P[t])
			N[++j]=++t;
		else
			t=N[t];
	}
	return N;
}

int main()  
{
	char P[10010];
	while(scanf("%s",P)!=EOF)
	{
		int len1=strlen(P);
		BuildNext(P);
		while(P[len1]!=0)
		{
			printf("%d\\n",P[len1]);
			len1=P[len1];
		}
	}
}

BM

  1. 区别:KMP:善于利用经验 BM:乐于借鉴教训
  2. 单次匹配概率越小,BM性能越明显(字母表越大,匹配概率越小)
  3. BM_GC(好后缀策略):最好n/m,最坏n*m
posted @ 2021-05-30 20:08  兔子翻书  阅读(55)  评论(0)    收藏  举报