字符串匹配算法总结

字符串匹配算法

1. 问题描述

给定目标字符串 T[0..n-1] (基于 0 的数组,数组长度为 n ),和模式串 P[0..m-1] ,问P 可否匹配 T 中的任意子串,如果可以,返回匹配位置。

2.问题分析

直观分析

暴力法,适用于较小规模的字符串匹配。

优化

主要有3=种优化办法,分别具体为: Rabin-Karp 算法,有限自动机和 KMP 算法。

1、Rabin-Karp 算法

Rabin-Karp 算法(以下简称为 RK 算法),是基于这样的思路:即把串看作是字符集长度进制的数,由数的比较得出字符串的比较结果。例如,给定字符集为∑ ={0,1,2,3,4,5,6,7,8,9} ,∑长度为 d=10 ,那么任何以∑为字符集的串都可看作 d (此处为 10 )进制的数。记模式串 P[0..n-1] 对应的数值为 P , T[0..n-1] 所有长度为 m 的子串对应的数值为 ts ,设 P 和 T 都是基于字符集长度为 |∑ |=d 的字符串。

那么, ts 即为 T[s..s+m] 对应的数值,这里 0<=s<=n-m-1 。

P = P[m]+d*(P[m-1]+d*(P[m-2]+..)))

同样 t0 也可类似求得。

最重要的是如何从 ts 求出 ts+1 。ts+1 =T[s+m]+d*(ts -d^(m-1) *T[s])注:此处是该算法的关键,即在常数时间内能够计算出下一个 m 长度的字串对应的数值。初看比较抽象,举个例子就比较明白了,设 x=12345 ,现在是已知长度为 3 的数值 234 ,现在要求 345 对应的数值,可以这样来得到: 345 = 5 + 10*(234-102 *2)

2、算法描述

求出所有 m 长度子串所对应的数值,对数值进行比较,继而得出子串是否匹配。当模式串长度很大时,这时对应的数值会很大,比较起来比较麻烦,可使用对一个大奇数取模后进行比较。

3、具体实现      

这里实现的只是m值较小时的情形,大整数需要特定的类的支持(如可自定义大整数类),选取10进制的数是为了方便起见,当然字母也是OK的。

#include <iostream>
#include <string>
#include <cmath>
using namespace std;

// get the value of the character in the set 
int getV(char p, string set)
{
	for(int i=0; i<set.length(); i++)
	{
		if (p==set[i])
			return i;
	}
	return -1;
}
// d is the size of the character set
int RK(string T, string P,string set)
{
	int d = int(set.length());
	int n = T.length();
	int m = P.length();
	int h = pow(double(d), m-1);
	int p=0;
	int t = 0;
	for(int i=0; i<m; i++)
	{
		p = d*p + getV(P[i],set);
		t = d*t + getV(T[i], set);
	}
	for (int s=0; s<=n-m; s++)
	{
		cout<<"p,t is "<<p<<","<<t<<endl;
		if (p==t)
			return s;
		if (s<n-m)
			t = getV(T[s+m],set)+d*(t-h*getV(T[s],set));
	}
	return -1;
}
int main()
{
	// set is the character set
	string set= "0123456789";
	// pattern P
	string P = "2365";
	// T is the string to match
	string T = "258569236589780";
	int i = RK(T, P, set);
	cout<<"the postition is:"<<i<<endl;
	return 0;
}

2、KMP算法

简单c实现

#include <stdio.h>
#include <string.h>
void get_next(char * T,int next[], int len)
{//求next数组
	int i=1;
	next[1]=0;			//next数组的第一个元素无作用
	int j=0;
	while(i<len)
	{
		if(j==0||T[i-1]==T[j-1])
		{
				++i;
				++j;
				next[i]=j;
		}
		else
		{
			j=next[j];
		}
	}
}

int  Index_KMP(char * S, char * T,int slen,int tlen,int pos,const int *next)
{//用KMP求字串在主串的位置
	int i =pos;
	int j = 1;
	while(i<=slen&&j<=tlen)
	{
		if(j==0 || S[i-1]==T[j-1])
		{
			++i;
			++j;
		}
		else
			j=next[j];
	}
	if(j>tlen)
		return i-tlen;
	else
		return 0;
}

int main()
{
	char *T="abaabcac";
	char *S="acabaabaabcacaabc";
	int tlen=strlen(T);
	int slen=strlen(S);
	int next[10]={0};

	get_next(T,next,tlen);

	int i=1;
	while(i<=tlen)
	{
		printf("%d\t",next[i++]);
	}
	printf("\n");
	int kmp=Index_KMP(S,T,slen,tlen,1,next);
	printf("The postion is %d\n",kmp);
	return 0;
}


3、有限自动机

最长回文字串

1、Manacher算法

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define M 20000050

char str1[M],str[2*M];//start from index 1
int rad[M],nn,n;

void Manacher(int *rad,char *str,int n)/*str是这样一个字符串(下标从1开始):
举例:若原字符串为"abcd",则str为"$#a#b#c#d#",最后还有一个终止符。
n为str的长度,若原字符串长度为nn,则n=2*nn+2。
rad[i]表示回文的半径,即最大的j满足str[i-j+1...i] = str[i+1...i+j],
而rad[i]-1即为以str[i]为中心的回文子串在原串中的长度*/
{
    int i;
    int mx = 0;
    int id;
    for(i=1; i<n; i++)
    {
        if( mx > i )
			rad[i] = rad[2*id-i]<mx-i?rad[2*id-i]:mx-i;        
        else
            rad[i] = 1;
        for(; str[i+rad[i]] == str[i-rad[i]]; rad[i]++)
            ;
        if( rad[i] + i > mx )
        {
            mx = rad[i] + i;
            id = i;
        }
    }
}

int main()
{
	int i,ans,Case=1;
	while(scanf("%s",str1)!=EOF)
	{
		nn=strlen(str1);
		n=2*nn+2;
		str[0]='$';
		for(i=0;i<=nn;i++)
		{
			str[2*i+1]='#';
			str[2*i+2]=str1[i];
		}
		Manacher(rad,str,n);
		ans=1;
		for(i=0;i<n;i++)
			ans=rad[i]>ans?rad[i]:ans;
		printf("%d\n",ans-1);
	}
return 0;
}











   

你可能感兴趣的:(字符串匹配算法总结)