在一个字符串中查找是否出现子字符串

今天写一篇帖子,讨论如何判断在一个字符数为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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值