字符串匹配问题

题目要求:
分别用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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值