字符串的匹配算法(BF+KMP)

文章介绍了字符串匹配的两种算法:BF算法(BruteForce)和KMP算法。BF算法是一种简单的暴力匹配,时间复杂度为O(m*n)。KMP算法利用匹配失败后的信息减少匹配次数,通过next数组实现模式串的非回溯匹配,时间复杂度为O(m+n)。文章提供了两种算法的C语言实现,并提到了KMP算法的优化——nextval数组。
摘要由CSDN通过智能技术生成


字符串的匹配算法

一、BF算法

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

1.BF代码实现:

/*
* 程序名:kmp.c,此程序演示了字符串的匹配模式算法,包括:
* 1)普通的模式匹配算法,即BF算法;
* 2)KMP算法的求next数组;
* 3)KMP算法的求nextval数组;
* 4)KMP算法的实现。
*/

#include<stdio.h>
#include<string.h>

//BF算法
int index_bf(char sstr[],char tstr[]);

int main()
{
        printf("请输入带匹配的目标串:");
        char sstr[101]; memset(sstr,0,sizeof(sstr));
        fgets(sstr,100,stdin);
        sstr[strlen(sstr)-1]=0;
        printf("请输入匹配模式串:");
        char tstr[101]; memset(tstr,0,sizeof(tstr));
            fgets(tstr,100,stdin);
        tstr[strlen(tstr)-1]=0;
        
        printf("目标串:%s,长度为%d\n",sstr,strlen(sstr));
        printf("模式串:%s,长度为%d\n",tstr,strlen(tstr));
        printf("BF算法检验到模式串在目标串中的位置为:%d\n",index_bf(sstr,tstr));    
        
        return 0;
}

// 采用BF算法,查找在目标串sstr中模式串tstr出现的位置,字符串的起始位置从0开始。
// 只要在目标串sstr中找到了第一个模式串tstr,函数就返回。
// 成功返回模式串tstr在目标串sstr中第一次出现的数组下标,失败返回-1。
int index_bf(char sstr[],char tstr[])
{
        if( sstr==0 || tstr==0 )return -1;
        int pos1,pos2;          //双指针遍历字符串
             pos1=pos2=0;

        //      "hello world"
        //      "ll0 "
        while( pos1<strlen(sstr) && pos2<strlen(tstr) )
        {

                //逐个比较目标串和模式串的字符
                //如果相等,模式串和目标串同时后移
                if( sstr[pos1] == tstr[pos2] )
                {
                        pos1++;
                        pos2++;
                }
                //如果不相等,目标串回溯到本次匹配失败的起始位置+1,模式串回溯到第一个字符
                else{
                        pos1=pos1-pos2+1;
                        pos2=0;
                }
        }

        //匹配成功,返回模式串在目标串中的第一个位置                     
        if( pos2==strlen(tstr) )return pos1-strlen(tstr);
        return -1;
}                                      

测试实例:
在这里插入图片描述

二、KMP算法

1.定义

它的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。kmp算法的时间复杂度O(m+n)。

2.KMP算法的原理:

(1)在匹配的过程中,目标串的指不需要 回溯,只回溯模式串的指针;
(2)如果模式串和目标串前n个字符匹配成功,遇到匹配失败的字符时,模式串的指针回溯的位置由模式串的内容决定(回溯到 匹配失败位置前的模式串内容的最长公共前后缀 的长度+1),然后继续比较。

3.KMP算法的实现

(1)根据模式串的内容,生成一个遇到匹配失败的字符时模式串的指针回溯的位置的数组(next 数组)。
下面代码中函数getnext();实现了生产next数组;

(2)在BF算法上稍作修改,当匹配失败时,模式串的位置指针回溯next数组指示的位置。

代码实现:

/*
* 程序名:kmp.c,此程序演示了字符串的匹配模式算法,包括:
* 1)普通的模式匹配算法,即BF算法;
* 2)KMP算法的求next数组;
* 3)KMP算法的求nextval数组;
* 4)KMP算法的实现。
*/
//KMP算法
 //根据模式串tstr,计算next数组,字符串的起始位置从0开始。
void getnext(char tstr[],int next[]);
int index_kmp(char sstr[],char tstr[]);


int main()
{
        printf("请输入带匹配的目标串:");
        char sstr[101]; memset(sstr,0,sizeof(sstr));
        strcpy(sstr,"hello world");
        fgets(sstr,100,stdin);
        sstr[strlen(sstr)-1]=0;
        printf("请输入匹配模式串:");
        char tstr[101]; memset(tstr,0,sizeof(tstr));
        strcpy(tstr,"llo");
        fgets(tstr,100,stdin);
        tstr[strlen(tstr)-1]=0;
       printf("目标串%s,长度为%d\n",sstr,strlen(sstr));
        printf("模式串:%s,长度为%d\n",tstr,strlen(tstr));
/*      
        int next[strlen(tstr)];
        getnext(tstr,next);
        printf("模式串next数组值为:");
        int ii;
        for(ii=0;ii<strlen(tstr);ii++)
        {
                printf("%3d ",next[ii]);
        }
        printf("\n");
*/

        printf("KMP算法检验到模式串在目标串中的位置为:%d\n",index_kmp(sstr,tstr));
        //printf("BF算法检验到模式串在目标串中的位置为:%d\n",index_bf(sstr,tstr));
        return 0;
}

// 根据模式串tstr,计算next数组,字符串的起始位置从0开始。
void getnext(char tstr[],int next[])
{
        if( tstr==0 || next==0 )return ;

        int len=strlen(tstr);           //获取模式串字符长度
        //第一个位置固定填-1 第二个位置固定填0
        if(len ==0 )return ;
        if(len == 1){next[0]=-1; return ;}
        if(len == 2){next[0]=-1; next[1]=0; return ;}

        next[0]=-1;next[1]=0;
        //从第三个位置开始计算next指针(最长公共前后缀的长度加1)
        int ii;
        char str1[1001];
        char str2[1001];
        int maxlen=0;
        for(ii=2; ii<len; ii++)
 		{
                int jj;
                for(jj=1; jj<ii; jj++ )
                {
                        memset(str1,0,sizeof(str1));
                        memset(str2,0,sizeof(str2));

                        strncpy(str1,tstr,jj);                  //获取前缀
                        strncpy(str2,tstr+ii-jj,jj);            //获取后缀
                        //printf("前缀=%s,后缀=%s\t",str1,str2);        
                        //前缀等于后缀,记录其长度      
                        if( strcmp(str1,str2)==0 )maxlen=jj;
                }
                //printf("\n"); 
                next[ii]=maxlen;
                maxlen=0;
        }
        return ;
}

// 采用kmp算法,查找在目标串sstr中模式串tstr出现的位置,字符串的起始位置从0开始。
// // 只要在目标串sstr中找到了第一个模式串tstr,函数就返回。
// // 成功返回模式串tstr在目标串sstr中第一次出现的数组下标,失败返回-1。
int index_kmp(char sstr[],char tstr[])
{
        if( sstr==0 || tstr==0 )return -1;
        int pos1,pos2;          //双指针遍历字符串
        pos1=pos2=0;

        // 获取next数组。
        int next[strlen(tstr)];
        getnext(tstr,next);
		
		//int nextval[strlen(tstr)];
		//getnextval(tstr,next,nextval);
		
        int slen=strlen(sstr);
        int tlen=strlen(tstr);
        //      "hello world"
        //      "ll0 "
        //while( pos1<(strlen(sstr)) && (pos2<(strlen(tstr))) )  用此行代码不行,第二次循环会直接跳出,原因不知道
        while( pos1<slen && pos2<tlen )
        {
         		//逐个比较目标串和模式串的字符
                //如果相等,模式串和目标串同时后移
                if( pos2==-1 || sstr[pos1]==tstr[pos2] )
                {
                        pos1++;
                        pos2++;
                }
                else{
                        pos2=next[pos2];
                        pos2=nextval[pos2];
                }
        }
        //匹配成功,返回模式串在目标串中的第一个位置
        if( pos2 == strlen(tstr) )
        { return (pos1-strlen(tstr));}
        return -1;
}

测试实例:
在这里插入图片描述

4.KMP算法的优化

用一个例子说明优化的必要性:
目标串: aaaabaaaacaaaadaaaaa
模式串: aaaaa
模式串next数组为: -1 0 1 2 3

第一次不匹配发生的位置 :

在这里插入图片描述

此时模式串进行回溯,回溯到 next[4]的位置,其值仍然为a

第二次发生不匹配的位置:

在这里插入图片描述
此时模式串继续回溯,回溯到 next[3]的位置,其值仍然为a,以此类推,直到回溯到next[0],目标串才会向后移动一个位置;

所以有必要对kmp算法进行优化,
在进行回溯的时候,如果发现 回溯位置的元素值 等于 当前匹配失败位置 的值,那么直接将该位置nextval的值 设置为待回溯的位置的nextval 的值 ;
用一个 nextval 数组来存放 每个位置 回溯的位置;

代码实现:

// 根据模式串tstr和next数组计算nextval数组,字符串的起始位置从0开始。
// void getnextval(char *tstr,int *next,int *nextval)
void getnextval(char tstr[],int next[],int nextval[])
{
  if ( (tstr==0) || (next==0) || (nextval==0) ) return;  // 判断空指针。

  int tlen=strlen(tstr);  // 模式串的长度。

  nextval[0]=-1;   // 位置0直接填入-1。

  int ii;

  // 从第1个位置开始扫描。
  for (ii=1;ii<tlen;ii++)
  {
  	//当前位置的元素值等于回溯位置的元素值
    if (tstr[ii]==tstr[next[ii]])nextval[ii]=nextval[next[ii]];
    else
         nextval[ii]=next[ii];
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值