字符串匹配:用随机算法
给定两个字符串:X=x1,…,xn,Y=y1,…,ym,看Y是否为X的子串?(即Y是否为X中的一段。)(此问题还可用Rabin-Karp算法、Boyer-Moore算法等)
一、随机算法Monte Carlo(用brute-force 思想)
记X(j)=xjxj+1…xj+m-1(从X的第j位开始、长度与Y一样的子串),从起始位置j=1开始到j=n-m+1,我们不去逐一比较X(j)与Y,而仅逐一比较X(j)的指纹Ip(X(j))与Y的指纹Ip(Y)。
由于Ip(X(j+1))可以很方便地根据Ip(X(j))计算出来,故算法可以很快完成。
不失一般性,设xi(1≤i≤n) 和yj(1≤j≤m)∈{0,1},即X, Y都是0-1串。
Ip(X(j+1)) = (xj+1…xj+m)(mod p)
=(2(xj+1…xj+m-1)+xj+m)(mod p)
=(2(xj+1…xj+m-1)+2mxj-2mxj+xj+m)(mod p)
=(2(xjxj+1…xj+m-1)-2mxj+xj+m)(mod p)
(∵(xy+z)(mod p)=(x(y mod p)+z)(mod p))
=(2 ( (xjxj+1…xj+m-1)mod p )-2mxj+xj+m)(mod p)
=(2*Ip(X(j))-xj+xj+m)(mod p) (﹡)
∴Ip(X(j+1))可以利用Ip(X(j))及(﹡)式计算出来。
算法
1、 随机取一个小于M的素数p,置j←1;
2、 计算Ip(Y)、Ip(X(1))及Wp(=2m mod p);
3、 While j≤n-m+1 do
{if Ip(X(j))=Ip(Y) then return j /﹡X(j)极有可能等于Y﹡/
else{使用(﹡)式计算出Ip(X(j+1));j增1}
}
4、 return 0; /﹡X肯定没有子串等于Y﹡/
时间复杂度:
Y、X(1)、2m均只有m位(二进制数),故计算Ip(Y)、Ip(X(1))及2m mod p的时间不超过O(m)次运算。
Ip(X(j+1))的计算:由于2*Ip(X(j))只需要在Ip(X(j))后加个0;当xj为1时,第二部分Wp*xj就是Wp,当xj为0时该部分为0;xj+m或为0或为1,然后进行加减法(O(1)时间)就可得到2*Ip(X(j))-xj+xj+m。但此式还要对p取模。
由于0≤2*Ip(X(j))≤2p-2,0≤xj≤p-1,0≤xj+m≤1,因此2*Ip(X(j))-xj+xj+m的值在[-(p-1), 2p-1]之间。故实际计算时,若上式是负值,则加上p后即得Ip(X(j+1));
若为非负,则看其是否小于p,小于p则已得Ip(X(j+1));
若大于等于p,则减去p后即得Ip(X(j+1))。
故Ip(X(j+1))的计算只需用O(1)时间。
由于循环最多执行n-m+1次,故这部分的时间复杂度为O(n)。于是,总的时间复杂性为O(m+n)。
失败的概率:当Y≠X(j),但Ip(Y)=Ip(X(j))时产生失败。Ip(Y)=Ip(X(j)) 当且仅当p能整除|Y-X(j)|。当p能整除|Y-X(j)|时,p当然也能整除|Y-X(1)| |Y-X(2)|…|Y-X(j)|…|Y-X(n-m+1)|(∵p素数,反之也成立),由于|Y-X(j)|不超过m个二进制位,
∴|Y-X(j)|<2m。
∴|Y-X(1)| |Y-X(2)|…|Y-X(n-m+1)| < (2m)n-m+1≤2mn。
由数论定理2(如果a<2n,则能够整除a的素数个数不超过p(n)个),能整除|Y-X(1)| |Y-X(2)|…|Y-X(n-m+1)|的素数个数不超过p(mn)个。
于是Pr[failure]=(Y不含在X中、但p(p<M)能够整除|Y-X(1)| |Y-X(2)|…|Y-X(j)|…|Y-X(n-m+1)|的素数的个数)/小于M的素数的个数
≤p(mn)/ p(M) = p(mn)/ p(2mn2) (取M=2mn2)
≈(mn/loge(mn))/(2mn2/loge(2mn2))= loge(2mn2)/2n loge(mn)
(m≥2时有)≤loge((mn)2)/2n loge(mn)=1/n
即失败的概率只与X的长度有关,与Y的长度无关。
当m=n时,问题退化为判定两个字符串是否相等的问题。
二、Las Vegas算法:
当Ip(Y)=Ip(X(j))时,不直接return j,而去比较Y和X(j), 即在return j之前加一个判断看Y和X(j)是否相等,相等则return j ,否则继续执行循环。这样,如果有子串X(j)与Y相匹配,该算法总能给出正确的位置即算法出错的概率为0)。
∵在最坏情况下算法执行O(mn)时间,而p能整除|I(Y)-I(X(j))|概率的不超过,故
算法的时间复杂性的期望值不超过。
三、KMP算法
KMP字符串模式匹配通俗点说就是一种在一个字符串中定位另一个串的高效算法。简单匹配算法的时间复杂度为O(m*n);KMP匹配算法。可以证明它的时间复杂度为O(m+n).。
先来看一个简单匹配算法的函数:
int Index_BF ( char S [ ], char T [ ], int pos )
{
/*若串 S中从第pos(S的下标0≤pos<StrLength(S))个字符
起存在和串 T相同的子串,则称匹配成功,返回第一个
这样的子串在串 S中的下标,否则返回 -1 */
int i = pos, j = 0;
while ( S[i+j] != '/0'&& T[j] != '/0')
if ( S[i+j] == T[j] )j ++; //继续比较后一字符
else
{
i ++; j = 0;//重新开始新的一轮匹配
}
if ( T[j] == '/0')return i; //匹配成功 返回下标
Else return -1;//串S中(第pos个字符起)不存在和串T相同的子串
}// Index_BF
此算法的思想是直截了当的:将主串S中某个位置i起始的子串和模式串T相比较。即从 j=0起比较 S[i+j]与 T[j],若相等,则在主串 S中存在以 i为起始位置匹配成功的可能性,继续往后比较( j逐步增1 ),直至与T串中最后一个字符相等为止,否则改从S串的下一个字符起重新开始进行下一轮的"匹配",即将串T向后滑动一位,即 i增1,而 j退回至0,重新开始新一轮的匹配。
程序代码:
// Match.cpp 文件;
#include<windows.h>
#include<iostream>
#include<sstream>
#include<cmath>
#include<ctime>
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
using namespace std;
//获得prefix数组
int* GetPrefixValue(char* strPattern, int iPatternLen)
{
int i, j; /* i runs through the string, j counts the hits*/
int* prefix = (int*)malloc(iPatternLen*sizeof(int));
i = 1;
j = 0;
prefix[0] = 0;
while(i<iPatternLen)
{
if(strPattern[i] == strPattern[j])
{
prefix[i] = ++j;
}
else
{
j = 0;
prefix[i] = j;
}
i++;
}
return prefix;
}
//返回target串在pattern串中第一次匹配的index
int KMPStringMatch(char* strPattern, int iPatternLen, char* strTarget, int iTargetLen, int* prefix)
{
int i = 0;
int j = 0;
while(i<iPatternLen && j<iTargetLen)
{
if(j==0 || strPattern[i]==strTarget[j])
{
i++;
j++;
}
else
{
j = prefix[j];
}
}
free(prefix);
if(j==iTargetLen)
{
return i-j; }
else
{
return -1;
}
}
/****kmp算法求字符串匹配****/
int KMP(char* strPattern, char* strTarget)
{
int* prefix = GetPrefixValue(strPattern, strlen(strPattern));
int index = KMPStringMatch(strPattern, strlen(strPattern), strTarget, strlen(strTarget), prefix);
return index;
}
/*********二进制转化为十进制*********/
double bintoint(double num)
{
double res=0;
double temp,n=1;
while(num!=0)
{
temp=(int)num%10;
num=(int)num/10;
res+=temp*n;
n*=2;
}
return res;
}
/*********十进制转换为二进制*********/
double inttobin(double num)
{
double res=0;
double temp,n=1;
while(num!=0)
{
temp=(int)num%2;
res+=temp*n;
num=(int)num/2;
n*=10;
}
return res;
}
/************用Monte Carlo算法求字符串匹配*************/
bool MonteCarlo(string& strA,string& strB)
{
int lenA=(int)strA.size(),lenB=(int)strB.size();// 取两字符串的长度
if(lenA<lenB) return false;
double numB;
numB=atoi(strB.c_str());
numB=bintoint(numB);
//产生随机数p
double M=2*lenB*lenB;
double p=1+(double(rand()) / double(RAND_MAX))*(M-1);
ostringstream oss;
oss<<p;
string str(oss.str());
str=str.substr(0,str.find('.'));
p=atoi(str.c_str());
//计算strB的指纹
cout<<p<<'/n';
double a=bintoint(atoi(strA.substr(0,lenB).c_str()));
double IpA=(long)a%(long)p;
double IpB=(long)numB%(long)p;
long Wp=(long)(pow(2.0,lenB))%(long)p;
int Xj=atoi(strA.substr(0,1).c_str());
int Xj_m=atoi(strA.substr(lenB,1).c_str());
cout<<inttobin(IpA)<<'/n';
for(int i=0;i<=lenA-lenB+1;i++)
{
if (IpA==IpB) return true;
IpA=(long)(2*IpA-Wp*Xj+Xj_m)%(long)p;
if(IpA<0) IpA=IpA+p;
if(i==lenA-lenB) break;
cout<<inttobin(IpA)<<'/n';
Xj=atoi(strA.substr(i+1,1).c_str());
Xj_m=atoi(strA.substr(lenB+1+i,1).c_str());
}
return false;
}
/************用Las Vegas算法求字符串匹配*************/
bool LasVegas(string& strA,string& strB)
{
int lenA=(int)strA.size(),lenB=(int)strB.size();
if(lenA<lenB) return false;
double numB;
numB=atoi(strB.c_str());
numB=bintoint(numB);
//产生随机数p
double M=2*lenB*lenB;
double p=1+(double(rand()) / double(RAND_MAX))*(M-1);
ostringstream oss;
oss<<p;
string str(oss.str());
str=str.substr(0,str.find('.'));
p=atoi(str.c_str());
//计算strB的指纹
cout<<"p is selected:"<<p<<'/n';
double a=bintoint(atoi(strA.substr(0,lenB).c_str()));
double IpA=(long)a%(long)p;
double IpB=(long)numB%(long)p;
long Wp=(long)(pow(2.0,lenB))%(long)p;
int Xj=atoi(strA.substr(0,1).c_str());
int Xj_m=atoi(strA.substr(lenB,1).c_str());
cout<<inttobin(IpA)<<'/n';
for(int i=0;i<=lenA-lenB+1;i++)
{
if (IpA==IpB)
{
if(strA.substr(i,lenB)==strB)
{
return true;
}
}
IpA=(long)(2*IpA-Wp*Xj+Xj_m)%(long)p;
if(IpA<0) IpA=IpA+p;
if(i==lenA-lenB) break;
cout<<inttobin(IpA)<<'/n';
Xj=atoi(strA.substr(i+1,1).c_str());
Xj_m=atoi(strA.substr(lenB+1+i,1).c_str());
}
return false;
}
int main()
{
system("color FC");
double start,finish,duration;
string strA="10100010100011111",strB="0010100";
char strC[]="10100010100011111";
char strD[]="0010100";
start=clock();
cout<<"KMP算法:"<<endl;
if(KMP(strC,strD)>=1)
{
cout<<"Equal/n";
cout<<"起始位置为:"<<KMP(strC,strD)<<endl;
}
else
{
cout<<"Unequal/n";
cout<<"字符串不匹配!"<<endl;
}
finish=clock();
duration=(finish-start)/CLOCKS_PER_SEC*1000;
cout<<"KMP算法 need the time:"<<duration<<endl;
start=clock();
cout<<endl<<"MonteCarlo算法:"<<endl;
for(int i=0;i<3;i++)
{
if(MonteCarlo(strA,strB)==true)
{
cout<<"Equal/n";
}
else
{
cout<<"Unequal/n";
}
}
finish=clock();
duration=(finish-start)/CLOCKS_PER_SEC*1000;
cout<<" MonteCarlo need the time:"<<duration<<endl;
start=clock();
cout<<endl<<"LasVegas算法:"<<'/n';
for(int j=0;j<3;j++)
{
if(LasVegas(strA,strB)==true)
{
cout<<"Equal/n";
}
else
{
cout<<"Unequal/n";
}
}
finish=clock();
duration=(finish-start)/CLOCKS_PER_SEC*1000;
cout<<"LasVegas need the time:"<<duration<<endl<<endl;
return 0;
}
四、结果分析:
上面三种算法中:
(1)Kmp是传统的用于字符串匹配求解方法,让子串向右移动尽可能远的一段距离来达到降低时间复杂度的目的,
Las Vegas和Monte Carlos算法为随机算法的两大基本算法:
(2)LasVegas算法总是给出正确的结果,但在少数应用中,可能出现求不出解的情况。
此时需再次调用算法进行计算,直到获得解为止。对于此类算法,主要是分析算法的时间复杂度的期望值,以及调用一次产生失败(求不出解)的概率。
(3)Mont Carlo算法通常不能保证计算出来的结果总是正确的,一般只能断定所给解的正确性不小于p(<p<1)。通过算法的反复执行(即以增大算法的执行时间为代价),
能够使发生错误的概率小到可以忽略的程度。由于每次执行的算法是独立的,故k次执行均发生错误的概率为(1-p)k。对于判定问题(回答只能是“Yes”或“No”),