题目要求:
分别用KMP、Monte Carlo和Las Vegas算法编写3个程序,并随机生成不小于5000对、长度较长、且长度不等的01串X和Y(三个程序处理相同的串)
统计算法的执行时间、Monte Carlo算法的出错率,并根据运行结果对三种算法进行深入的比较
长度可为50、500、5000、50000位,Y不能太短
主要思路:
KMP算法
主要思路是,先用i,j分别表示主串X和模式串Y中当前正比较的字符位置,从主串X的第pos个字符起,与串Y的第一个字符比较,如果相等,则继续比较后继字符,否则需利用比较过的信息,i指针不需要回溯,仅将子串Y后移一段位置,从这个位置开始继续从子串的第一个字符与主串比较。
这个位置的判断,需要引进一个数组next,用于存放子串的部分匹配值,移动的位数=已匹配的字符数-对应的部分匹配值,move=(j-1)-next[j-1]这样的话,每次失配后,就去找前一个字符的部分匹配值即可;也可以将next数组右移一位,将前一个字符的部分匹配值给自己保存,这样失配后,用自己对应的部分匹配值即可,move=j-1-next[j],j=j-move=j-( j-1-next[j])=next[j]+1;还可以进一步把next数组每个值加1,这样,j=next[j]即可。然后将主串和子串开始匹配,当匹配过程产生失配时,指针s不变,指针t退回到next[t]的位置重新进行比较,且当t=0时;指针s,t同时加1,即主串第s个位置和模式串第1个字符不等时,从主串第s+1个位置开始匹配。
基于上述思路,生成5000对长度不等的01串主串和子串,运行后5000对匹配成功183对,总匹配时间为3175ms。
我发现,当主串和子串匹配失败时,所用时间为0ms,而当主串中包含子串时,则会有10秒左右的匹配时间。
Monte Carlo算法
主要思路是考虑随机算法(用brute-force思想),逐个选出主串中与子串长度相等的串X[j](j=1~~n-m+1),与子串Y比较。但是为了比较的方便,我们也要引入一个“工具”—指纹,通过比较X[j]的指纹Ip (X[j])和Y的指纹Ip (Y)是否相同,来确认主串中是否包含子串,其中Ip (X[j])=I(X[j])(mod p),这里的p是一个小于M的素数,M可以根据具体需要来调整。如果Ip (X[j])≠Ip (Y),则X[j]≠Y,但是若Ip (X[j])=Ip (Y)时,X[j]也不一定等于Y,我们把Ip (X[j])=Ip (Y),X[j]≠Y称为一个误匹配,通过统计误匹配的次数来确定Monte Carlo算法的出错率。
第一步是生成素数p,这里我采用a=min+(rand()%(max-min));来生成。在这里我遇到一个问题。因为之前在随机生成01串的主串和子串时,我已经使用过srand((unsigned)time(NULL));来生成随机数种子,然后在生成随机素数的时候我又使用了一次来生成随机数,导致生成的素数不变,只有一个数,然后把生成随机素数中的这条语句删去后就变得正常了。生成随机数后要对其进行判断,如果不是素数要重新生成。
第二步是取指纹。先取X(1)和Y的指纹IpX=IpX2+X[k]-‘0’;//(X+pos-1,Ylen,p); IpY=IpY2+Y[k]-‘0’;//getIP(Y,Ylen,p);IpX=(IpX%p+p)%p;IpY=(IpY%p+p)%p;将它两的指纹进行对比,如果相同,就表明Y在X中的起始位置是1,j=1,返回j值,如果不相同,就计算X(j+1)的指纹,Ip(X(j+1))=((2*Ip(X(i))-(2^m mod p)*X[j]+X[j+Ylen])mod p;这里,我先用wp来计算2^m mod p的值。如果当j从1到Xlen-Ylen+1都找不到和Y相同指纹的串,就返回0,表明匹配失败。部分典型匹配结果如下图。最前面的数值为该匹配过程中使用的素数733和137。
Las Vegas算法
主要是对MonteCarlo算法的改进,当Ip(X(j))=Ip(Y)时,不直接return j,而是比较X(j)与Y是否相等,若相等才return j,否则继续执行循环。这样就能保证该算法总能给出正确的匹配结果。匹配的部分结果如下:
结果
将不同长度主串和子串的匹配结果和运行结果统计到如下表格中(测试对数均为5000对):
X的长度 Y的长度 KMP Monte Carlo Las Vegas
ms 个数 ms 个数 出错个数 出错率 ms 个数
50 10 3917 216 2853 252 36 0.72% 2608 216
10 4017 175 2786 206 31 0.62% 2770 174
10 3914 162 2780 186 24 0.48% 2529 162
10 3928 199 2841 232 33 0.66% 2574 199
从上表中可以看出,(1)运行时间基本遵循KMP> Monte Carlo> Las Vegas; (2)Monte Carlo算法的出错率与所选素数的大小有关,所选素数越大,出错率越小。
#include <iostream>
#include <fstream>
#include <time.h>
#include <stdlib.h>
#include <math.h>
using namespace std;
int KMP(char *X,char *Y,int Xlen,int Ylen)
{
int next[50];//next数组存放子串Y中每个元素对应的部分匹配值
next[1]=0;//next数组第一个元素初始化为0;右移后,第一个元素用-1填充,后来每个值都加1后,也为0
int s=1,t=0;
while(s<Ylen)//遍历子串Y,找出所有的next[s]
{
if(t==0||Y[s]==Y[t])
{
++s;++t;next[s]=t;
}
else
t=next[t];
}
cout<<"next数组为:";
for(t=1;t<=Ylen;t++)
{
cout<<next[t];
}
s=1;//主串X的指针,用来取出s位置X中的字符
t=1;//主串Y的指针,用来取出t位置Y中的字符
while(s<=Xlen&&t<=Ylen)
{
if(t==0||X[s]==Y[t])
{
s++;
t++;
}
else t=next[t];
}
if(t>Ylen)
return s-Ylen;
else
return -1;
}
bool isprime(int n) //测试一个整数是否为素数
{
for(int i=2;i<sqrt((double)n);i++)
{
if(n%i==0)//输出:若n为素数,则返回true;否则false
return 0;
else
continue;
}
return 1;
}
int random_prime(int min, int max) //随机产生一个[min, max-1]区间上的素数
{
int a=0;
while(1)
{
a=min+(rand()%(max-min));
if(isprime(a)==1)
return a;
else
continue;
}
}
int MonteCarlo(char *X,char *Y,int Xlen,int Ylen,int p)
{
int k,j=1,pos=1;
int IpX=0, IpY=0, wp=1;
for(k=1;k<=Ylen;k++)//取指纹
{
IpX=IpX*2+X[k]-'0';
IpY=IpY*2+Y[k]-'0';
IpX=(IpX%p+p)%p;
IpY=(IpY%p+p)%p;
}
//计算wp=2Ylen mod p
for(k=1; k<=Ylen;k++)
{
wp=wp*2;
}wp=(wp%p+p)%p;
//开始匹配模式串
for(j=1;j<=Xlen-Ylen+1;j++)
{
if(IpX==IpY)
{
return j;
}
else
{
IpX=((2*IpX-wp*(X[j]-'0')+(X[j+Ylen]-'0'))%p+p)%p;
/*if(IpX<0)
IpX+=p;
else if(IpX>=p)
IpX-=p; */
}
}
return 0;
}
int LasVegas(char *X,char *Y,int Xlen,int Ylen,int p)
{
int k,j=1,pos=1;
int IpX=0, IpY=0, wp=1;
for(k=1;k<=Ylen;k++)//取指纹
{
IpX=IpX*2+X[k]-'0';
IpY=IpY*2+Y[k]-'0';
IpX=(IpX%p+p)%p;
IpY=(IpY%p+p)%p;
}
//计算wp=2Ylen mod p
for(k=1; k<=Ylen;k++)
{
wp=wp*2;
}wp=(wp%p+p)%p;
//开始匹配模式串
for(j=1;j<=Xlen-Ylen+1;j++)
{
if(IpX==IpY)
{
for(k=1;k<=Ylen;k++)
{
if(X[j+k-1]!=Y[k])
return 0;
}
return j;
}
else
{
IpX=((2*IpX-wp*(X[j]-'0')+(X[j+Ylen]-'0'))%p+p)%p;
/*if(IpX<0)
IpX+=p;
else if(IpX>=p)
IpX-=p; */
}
}
return 0;
}
void main()
{
int Xlen,Ylen;
int prime[5000]; //存放MAXSIZE个随机产生的素数
cout<<"请分别输入主串和子串的最长长度: ";
cin>>Xlen>>Ylen;
int KMPnum=0;//统计KMP匹配成功的次数
int MCnum=0;//统计Monte Carlo匹配成功的次数
int LVnum=0;//统计Las Vegas匹配成功的次数
double KMPt=0;//统计KMP所用时间
double MCt=0;//统计Monte Carlo所用时间
double LVt=0;//统计Las Vegas所用时间
double start1, finish1;//统计KMP时间
double start2, finish2;//统计Monte Carlo时间
double start3, finish3;//统计Las Vegas时间
//srand(time(0)); //调用随机数
srand((unsigned)time(NULL));//种种子
int i,j,index1;
int index2,index3;
char *X=new char[Xlen];
char *Y=new char[Ylen];
for(i=0;i<5000;i++)
{
prime[i]=random_prime(Ylen*Ylen, 20000);//随机产生一个MonteCarlo和Las Vegas算法中所需要的素数
cout<<prime[i]<<" ";
cout<<"生成的主串为";
for(j=1;j<=Xlen;j++)//随机给主串X分配01字符串
{
//cin>>X[j];
X[j]=rand()%2+'0';
cout<<X[j];
}
cout<<endl;
cout<<"生成的子串为";
for(j=1;j<=Ylen;j++)//随机给子串Y分配01字符串
{
//cin>>Y[j];
Y[j]=rand()%2+'0';
cout<<Y[j];
}
cout<<endl;
//KMP算法
start1=clock();//开始统计KMP时间
index1=KMP(X,Y,Xlen,Ylen);
if(index1>=0)
{
cout<<"KMP算法数据匹配成功,子串的第一个元素位于主串的第"<<index1<<"个元素"<<X[index1]<<"处"<<'\n';
KMPnum++;//若成功,次数加1
}
else
cout<<"KMP算法数据匹配失败"<<'\n';
finish1=clock();
KMPt+=(finish1-start1);//统计5000对数组匹配所需要的时间
Monte Carlo算法
start2=clock();//开始统计Monte Carlo时间
index2=MonteCarlo(X,Y,Xlen,Ylen,prime[i]);
if(index2>0)
{
cout<<"Monte Carlo算法数据匹配成功,子串的第一个元素位于主串的第"<<index2<<"个元素"<<X[index2]<<"处"<<'\n';
MCnum++;//若成功,次数加1
}
else
cout<<"Monte Carlo算法数据匹配失败"<<'\n';
finish2=clock();
MCt+=(finish2-start2);//统计5000对数组匹配所需要的时间*/
//Las Vegas算法
start3=clock();//开始统计Monte Carlo时间
index3=LasVegas(X,Y,Xlen,Ylen,prime[i]);
if(index3>0)
{
cout<<"Las Vegas算法数据匹配成功,子串的第一个元素位于主串的第"<<index3<<"个元素"<<X[index3]<<"处"<<'\n';
LVnum++;//若成功,次数加1
}
else
cout<<"Las Vegas算法数据匹配失败"<<'\n';
finish3=clock();
LVt+=(finish3-start3);//统计5000对数组匹配所需要的时间*/
}
//Las Vegas算法
cout<<"KMP算法匹配成功的个数为:"<<KMPnum<<endl;
cout<<"KMP算法匹配所用的时间为:"<<KMPt<<"ms"<<endl;
cout<<"Monte Carlo算法匹配成功的个数为:"<<MCnum<<"匹配错误的个数为"<<MCnum-KMPnum<<endl;
cout<<"Monte Carlo算法匹配所用的时间为:"<<MCt<<"ms"<<endl;
cout<<"Las Vegas算法匹配成功的个数为:"<<LVnum<<endl;
cout<<"Las Vegas算法匹配所用的时间为:"<<LVt<<"ms"<<endl;
}