数据结构---串的模式匹配

1)串的模式 匹配的概念

设有两个串s和t,要在串s中找到与t相等的子串。通常将s称为目标串,t称为模式串,这种串的定位查找也称为模式匹配。

2)串的模式匹配算法有几种?具体说明。

常见的串的模式匹配算法有4种。

1.BF算法:

BF算法,即暴力(Brute Force)算法,是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法。

2.KMP算法:

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)。KMP算法是各大教科书上的看家绝学,曾被投票选为当今世界最伟大的十大算法之一;但是晦涩难懂,并且十分难以实现。

3.BM算法:

在计算机科学里,Boyer-Moore字符串搜索算法是一种非常高效的字符串搜索算法。它由Bob Boyer和J Strother Moore设计于1977年。此算法仅对搜索目标字符串(关键字)进行预处理,而非被搜索的字符串。虽然Boyer-Moore算法的执行时间同样线性依赖于被搜索字符串的大小,但是通常仅为其它算法的一小部分:它不需要对被搜索的字符串中的字符进行逐一比较,而会跳过其中某些部分。通常搜索关键字越长,算法速度越快。它的效率来自于这样的事实:对于每一次失败的匹配尝试,算法都能够使用这些信息来排除尽可能多的无法匹配的位置。

4.Sunday算法:

Sunday算法是Daniel M.Sunday于1990年提出的字符串模式匹配。其核心思想是:在匹配过程中,模式串发现不匹配时,算法能跳过尽可能多的字符以进行下一步的匹配,从而提高了匹配效率。目前发现的最高效且容易理解的算法。

3)  串的BF模式匹配算法思路是?如何实现?效率如何?

算法思路: 简单暴力的一个算法,如果遇到字符不匹配,主串i指针回溯到本次匹配位置的下一个位置,而模式串则重新回到0(开始的位置),开始下一轮的匹配。 成功匹配的条件是模式串指针j走到头,也就是j=length-1,length为模式串长度。跳出循环表示为j=length

代码实现:

int mate(char* B, char* A) {
    int i = 0, j = 0;
    while (i < strlen(B) && j < strlen(A)) {
        if (B[i] == A[j]) {
            i++;
            j++;
        }
        else {
            //匹配失败时,i 向后移动一位,j 重置
            i = i - j + 1;
            j = 0;
        }
    }
    //跳出循环有两种可能,i=strlen(B)说明已经遍历完主串,匹配失败;j=strlen(A),说明模式串遍历完成,在主串中成功匹配
    if (j == strlen(A)) {
        return i - strlen(A) + 1;
    }
    //运行到此,为 i==strlen(B) 的情况,模式匹配失败
    return -1;
}

时间复杂度:

例:S='0000000001',T= '0001',pos=1

        若n为主串长度,m为子串长度,最坏情况是

        主串前面n-m个位置都部分匹配到子串的最后一位,即这n-m位各比较了m次

        最后m位也各比较了1次

        总次数为:(n-m)*m+m=(n-m+1)*m

        若m<<n,则算法复杂度为O(n*m)

4)串的KMP模式匹配算法思路是?如何实现?效率如何?

算法思路:KMP算法中,每当一趟匹配过程中出现失配时,主串S中的i指针不需要回溯,而是利用已经得到的“部分匹配结果”,将模式串向右“滑动”尽可能远的一段距离后,继续进行比较,从而快速达到匹配结果。

代码实现:

int Index_KMP(char S[],int pos,char T[],int next[])
{
    int i,j;
    while(i<=strlen(S)&&j<=strlen(T))
    {
        if(j==0||S[i]==T[j])
        {   
            i++;
            j++;
        }
        else
        {
            j=next[j];
        }
        if(j>strlen(T))
            return i-strlen(T);
        else
            return 0;
    }   
}

时间复杂度:

假设m为模式串strM的长度,n为待匹配的字符串strN的长度。

O(m+n)=O( [m,2m]+ [n,2n] ) = 计算next数组的时间复杂度+遍历比较的复杂度。

也就是:
计算next数组时的比较次数介于[m,2m]。
遍历比较的比较次数介于[n,2n],最坏情形形如T=“aaaabaaaab”,P=“aaaaa”。
所以算法时间复杂度是O(m+n).
 

NEXT数组求解:

int GetNext(char T[],int next[])
{
    next[1]=0;
    int i=1,j=0;
    while(i<=strlen(T))
    {
        if(j==0||T[i]==T[j])
        {
            ++i;
            ++j;
            next[i]=j;
        }
        else j=next[j];
    }
}

例:T= a b a a b c a b a

                  1  2  3  4  5   6  7  8  9

位置 1 上的元素 a 前面没有子串,因此这里我们令 next[1] = 0
位置 2 上的元素 b,它前面的字符串为 a,字符串 a 没有最大相等前缀和后缀(注意:最大相等前缀后缀不包括自身),因此 next[2] = 1
位置 3 上元素的 a, 它前面的字符串为 ab,字符串 ab 没有最大相等前缀和后缀,因此next[3] = 1
位置4上元素的a, 它前面的字符串为aba,字符串aba的最大相等前缀和后缀为a,因此next[4] = 2
位置5上元素的b, 它前面的字符串为abaa,字符串abaa的最大相等前缀和后缀为a,因此next[5] = 2
位置 6 上的元素 c, 它前面的字符串为 abaab,字符串 abaab的最大相等前缀和后缀为 ab,因此 next[6] = 3
位置 7 上的 元素a, 它前面的字符串为 abaabc,字符串 abaabc的没有最大相等前缀和后缀,因此 next[7] = 1
位置 8 上的 元素b, 它前面的字符串为 abaabca,字符串 abaabca的最大相等前缀和后缀为 a,因此 next[8] = 2
位置 9 上的 元素a, 它前面的字符串为 abaabcab,字符串 abaabcab的最大相等前缀和后缀为 ab,因此 next[9] = 3
通过以上步骤,我们便可得到整个next数组的值,其余T的对应关系如下:

         T= a b a a b c a b a

  next = 0 1 1 2 2 3 1 2 3

如何通过代码求解:

对于模式串的位置j,有next[j] = k,例如取j = 4,则有next[4] = 1;则对于模式串的位置j + 1,有以下两种情况:
若 p[k] == p[j],即p[1] == p[4],则有next[j + 1] = next[j] + 1
若 p[k] != p[j],则令k = next[k],若 p[k] == p[j]next[j + 1] = k + 1,否则重复此过程程。

5)KMP模式匹配算法如何改进?nextval值如何求解?举例说明。

改进原因:KMP的next数组只分析了当前字符之前的字符串的相似度,而没有把当前字符考虑进去, 从而导致上述没有意义的比较操作。

那么如何才能把当前字符也考虑进去呢?

基本原理就是,在需要子串指针回溯时,进行当前位置元素与回溯之后位置元素比较,如果相等,那么就没有必要再进行比较了,子串的指针继续回溯。如此往复

因此,改进的KMP算法又添加了一个数组nextval, 它是在next基础之上计算出来的。

Nextval数组求解:

int GetNextval(char T[], int nextval[]){
	i=1;	
	j=0;	
	nextval[1]=0;
	while(i < strlen(T){	
		if(j==0 || T[i] == T[j])
        {	
			++i;	
            ++j;
			if(T != T[j])
				nextval[i]=j;	//当前的j值为nextval在i位置的值
			else
				nextval[i]=nextval[j];	//前缀字符==后缀字符,将前缀字符的nextval值赋值给            nextval在i位置的值
		}
		else
				j=nextval[j];	//字符不同,则左侧下标回溯
	}
}

例:

            T= a b a a b c a b a

     next = 0 1 1 2 2 3 1 2 3

nextval = 0 1 0 2 1 3 0 1 1

nextval数组第一个数直接为0。

nextval第二数:模式串第二个字符为b,对应的下标数组第二个数是1,那就是将模式串的第1个字符和b相比较,a!=b,所以直接将下标数组第二个数1作为next-val数组第二个数的值

第三个数:模式串第三个字符为a,对应下标数组第三个数为1,取其作为下标,找到模式串第1个字符为a,a=a,那取nextval的第一个数做为nextval第三个数的值,也就是0 。

第四个数:模式串第四个字符为a,对应下标数组第四个数为2,取其作为下标,找到模式串第2个字符为b,a!=b,所以直接将下标数组第四个数2作为nextval数组第四个数的值 

以此类推可求得其余nextval数组的值如上。                               

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值