理解KMP模式匹配算法

</pre><pre class="cpp" name="code">书上朴素的匹配算法(回朔主串ch1 i指针,子串ch2 j指针)

while (i <= ch1[0] && j <= ch2[0]){
		if(ch1[i] == ch2[j]){
			++i;
			++j;
			printf("%c", ch1[i]);
		}
		else{
			i = i-j+2;//回朔i
			j =1;//回朔j
		}
	}


我自己开始写的匹配算法(不回朔i,但回朔j)

while (i <= ch1[0] && j <= ch2[0]){
		if(ch1[i] == ch2[j]){
			++i;
			++j;
			printf("%c", ch1[i]);
		}
		else{
			i++;//不回朔i
			j =1;//回朔j
		}
	}

</pre></p><p>KMP算法。比我的更好,更复杂(不回朔i,j)(不带nextval[])</p><pre class="cpp" name="code">
int next[100];
void getnext(char b[]){
	int i=1,j=0;//i是每个位子,j是回退的位子
	next[1]=0;
	while(i <= strlen(b)){
		if(j == 0||b[i-1] == b[j-1]){
		i++;
		j++;
		next[i] = j;
		}
		else
		j=next[j];//用上一个的回退关系
	}
}
int kmp(char ch1[],char ch2[])
{
	int i=1,j=1;//i是主串中的位子,j匹配串的位子
	while(i<=strlen(ch1)&&j<=strlen(ch2))
	{
		if(j==0||ch1[i-1]==ch2[j-1])
		{
			i++;
			j++;
		}
		else
			j=next[j];
	}
	if(j>strlen(ch2))return i-strlen(ch2);
	else
	return 0;
}

手算next[i]值

我们令 next[0] = 0 。从 next[1] 开始,每求一个字符的 next 值,就看它前面是否有一个最长的"字符串"和从第一个字符开始的"字符串"相等(需要注意的是,这2个"字符串"不能是同一个"字符串")。如果一个都没有,这个字符的 next 值就是1;如果有,就看它有多长,这个字符的 next 值就是它的长度。

理解:就是说模式串自己和自己匹配,错一位开始自己匹配,当前字符前面有没有重合字符,如aasd的next值分别是0121,第一个a默认0,第二个a默认1(模式匹配时从模式串第一个开始,所以默认1),第三个s前面有个a(模式匹配时可以不回朔到最开始的a回朔到第二个a就好,位置是模式串的2)第四个d模式匹配只能从第1个来就是1。

计算修正后的 Nextval[i] 值:

我们令 nextval[0] = 0。从 nextval[1] 开始,如果某位(字符)与它 next 值指向的位(字符)相同,则该位的 nextval 值就是指向位的 nextval 值(nextval[i] = nextval[ next[i] ]);如果不同,则该位的 nextval 值就是它自己的 next 值(nextvalue[i] = next[i])。

理解:这个是升级版本,为了解决aaaab和aaabaaaaab的匹配问题,你可以看到当三a一b被匹配失败时,按原理还要进行第二个a第三个a的匹配,何不滑远点直接对b判断甚至对b后的a进行判断,这就是产生nextval的原因。相应的

  j        1 2 3 4 5

模式串 a a a a b

next     0 1 2 3 4

nextval0 0 0 0 4

void Nextval(char*ch2,int*next){
	i = 1; nextval[1] = 0; j = 0;
	while(i < strlen(ch2)){
		if( j == 0 || ch2[i] == ch2[j]){
			++i;
			++j;
			if(ch2[i] != ch2[j])
				nextval[i] = j;
			else nextval[i] = nextval[j];
		}
		else
			j = nextval;
	}
}

另外

<pre class="cpp" name="code">void GetNextEx(char*ch2,int*next)
{
	int k=1,j=0;
	next[1] = 0;
	while(k<strlen(T))
	{
		if(j == 0||T[k] == T[j])
		{
			++k;
			++j;
			if(T[k]==T[j])
			next[k]=next[j];
		else
			next[k]=j;
		}
		else
			j=next[j];
	}
}

 

和朴素算法相比,只是修改一句话而已(?),但是算法复杂度从O(m*n) 变成了:O(m+n)

完整代码by:recruits

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

#define RST_OVERFLOW -1;
#define RST_ERROR 0;
#define RST_OK 1;

int *nextVal=NULL;

int getNextVal(const char *pStr, int *nextVal){
    if(NULL == pStr || strlen(pStr) == 0){
        return RST_ERROR;
    }

    int j=0; // 用j循环模式串中的字符
    int k=-1; // 用k保存 nextVal 的值
    nextVal[0] = -1;

    while(j < strlen(pStr)){
        if(k == -1 || pStr[j] == pStr[k]){
            ++j; ++k;
            if(pStr[j] != pStr[k]){
                nextVal[j] = k;
            } else {
                nextVal[j] = nextVal[k];
            }
        } else {
            k = nextVal[k];
        }
    }

    return RST_OK;
}

int indexOfStrPos(const char *srcStr, const char *subStr, int pos){
    if(NULL == srcStr || NULL == subStr || pos < 0 || pos > strlen(srcStr) - 1){
        return RST_ERROR;
    }

    int i=pos,j=0;
    int srcLen = (int)strlen(srcStr);
    int subLen = (int)strlen(subStr);
    while(i < srcLen && j < subLen){
        if(j == -1 || srcStr[i] == subStr[j]){
            ++i; ++j;
        } else {
            j = nextVal[j];
        }
    }

    if(j >= subLen){
        return i - j;
    } else {
        return RST_ERROR;
    }
}

int indexOfStr(const char *srcStr, const char *subStr){
    return indexOfStrPos(srcStr, subStr, 0);
}

int main(int argc, const char * argv[]){
    char *test = "asldkalalskdblalskdl";
    char *p = "lalskdl";

    nextVal = (int *)malloc(strlen(p) * sizeof(int));
    getNextVal(p, nextVal);

    for (int i=0; i<strlen(p); i++) {
        printf("--->nextVal[%d] is :%d\n", i, nextVal[i]);
    }

    int rst = indexOfStr(test, p);
    int rst2 = indexOfStrPos(test, p, 15);
    printf("%d---%d\n", rst, rst2);
    return 0;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值