今天写一篇帖子,讨论如何判断在一个字符数为N的字符串s1中是否存在一个字符数为k(k<N)的子字符串s2,并返回第一次出现的位置,如果这篇文章对您有所帮助,不要吝啬对博主点赞哦
1.首先最一般的算法就是将s1进行分割,将字串与s2进行比较,这种算法最坏情形下的时间复杂度为O(k*(N-k)),平均时间为k*(1+N-k)/2
2.对第一种算法稍微进行一些优化,我们首先在s1中查找s2的第一个字符,找到之后对后续字符进行比对,这样使得平均情形有了较大改善
3.之后我应用了散列的想法,对这个问题重新进行考虑,假设s1字符串为A1A2……AN,s2字符串为P1P2……Pk,我们可以通过计算得到一个散列值Hp,并将该散列值与A1A2……Ak,A2A3……Ak+1,……进行比较,如果散列值匹配,那我们再逐字符进行比较。
只要hash函数设置恰当就可以实现如果AiAi+1……Ai+k-1已知那么Ai+1Ai+2……Ai+k可以以常数的时间算出。这里我写的hash函数如下
unsigned int Hash(string s)
{
unsigned int hashval ;
int k = s.size();
for(auto p : s)
hashval = (hashval<<5)+p%32;
return hashval%(int)pow(32,k-1);
}
通过移位可以在常数时间内计算出相邻子串的hash值,这种算法的最坏情形运行时间为O(k+N)加上反驳假匹配所需的时间
4.对上述算法加以改进,可以将hash的碰撞次数降为0,这样运行时间就为O(k+N)
unsigned int Hash(string s)
{
unsigned int hashval ;
int k = s.size();
for(auto p : s)
hashval = (hashval<<5)+p%32;
return hashval%(int)pow(32,k);
}
5.以上就是我利用已有知识所能想到的部分了,查阅相关论文后,了解到一个平均时间为O(N/k)的算法,下文将详细介绍这种算法,文献已标注在文章末尾。
为了方便表述,我们将s1称为string,s2称为pat
与第二种算法不同,这种算法通过pat中最右端的字符char来寻找子串,我们将与char对齐的string中字符的位置记为position,位置上的字符记为character
(1)如果character在pat中没有出现,我们可以将position右移k个单位,而不是1个单位
(2)如果character在pat中出现但与position不匹配,那我们从pat最右端寻找character第一次出现的位置,并通过移动使这两个位置对齐
(3)如果char与position匹配,那我们依次检查char左边紧挨的字符,重复前两个步骤
但是,到这里我们仍然存在改进的空间,在依次检查字符时,遇到一对不匹配的字符,这是我们将刚才已经检查过的字符取出来作为子串,通过递归调用查询出下一次该字符出现的位置,将这个偏移量与我们在(1)(2)中计算出的偏移量进行比较,取较大者作为position移动的值
以上叙述可能比较晦涩,下面以一个例子来进行说明(*为position)
pat: AT-THAT
string: ... WHICH-FINALLY-HALTS.--AT-THAT-POINT...
*
‘F’未在pat中出现,所以右移7位
pat: AT-THAT
string: ... WHICH-FINALLY-HALTS.--AT-THAT-POINT...
*
‘-’在pat中出现,移动使其对齐
pat: AT-THAT
string: ... WHICH-FINALLY-HALTS.--AT-THAT-POINT...
*
‘T’匹配,检查下一位
pat: AT-THAT
string: ... WHICH-FINALLY-HALTS.--AT-THAT-POINT...
*
‘L’不存在右移七位
pat: AT-THAT
string: ... WHICH-FINALLY-HALTS.--AT-THAT-POINT...
*
继续检查
pat: AT-THAT
string: ... WHICH-FINALLY-HALTS.--AT-THAT-POINT...
*
在右侧找到“AT”使之对齐
pat: AT-THAT
string: ... WHICH-FINALLY-HALTS.--AT-THAT-POINT...
*
全部匹配,检查完毕
下面附上伪代码
stringlen <---- length of string
i <---- patlen
top: if i > stringlen then return false
j<---- patlen
loop: if j=0 then return i+1
if string(i)=pat(j)
then
j<----j-1
i<----i-1
goto loop
close
i<----i+max(delta1(string(i)),delta2(j))
goto top
最后附上我自己的实现,供大家学习参考
/*
The expected value of the number of inspected characters in string is c*(i+patlen),where c<1 and gets smaller as patlen increases
*/
#include <string>
#include <iostream>
#include <math.h>
using namespace std;
int issubstr(int start,string pat,string str)
{
const int stringlen = str.size();
const int patlen = pat.size();
int i =start+patlen-1;
int j;
bool* inspect;
inspect = new bool[128];
for(auto k=0;k<i;k++)
inspect[pat[k]%128]=true;
int delta1,delta2;
string substr;
while(true){
if(i >stringlen-1) return -1;
j = patlen-1;
while(true)
{
if(j < 0) return i+1;
if(str[i]==pat[j])
{
j=j-1;
i=i-1;
}else break;
}
if(!inspect[str[i]%128])
delta1 = patlen;
else {
for(auto k=j-1;k>=0;k--)
if(pat[k]==str[i]){
delta1 = j-k;
break;
}
}
if(j!=patlen-1)
{
substr = pat.substr(j+1,patlen-j-1);
delta2= issubstr(i+2,substr,str)-i;
}else delta2=0;
if(delta2<0) return -1;
else i = i + max(delta1,delta2);
}
}
int main()
{
string str1,str2;
cout<<"Enter the str1: "<<endl;
cin>>str1;
cout<<"Enter the str2: "<<endl;
cin>>str2;
int n=issubstr(0,str1,str2);
if(n>0)
cout<<n;
else cout<<"Not Found!"<<endl;
}
> R.S.Boyer and J.S.Moore,"A Fast String Searching Algorithm",Communications of the ACM,20(1977),762-772