数据结构之字符串匹配

串是由零个或多个字符组成的有限序列,又名字符串。一般记为s="a1a2......an"(n≥0),其中,s是串的名称,用双引号括起来的字符序列是串的值。串中的字符数目n称为串的长度。零个字符的串称为空串,其长度为0。所谓的序列,说明串的相邻字符之间具有前驱和后继的关系。

字符串匹配即查找待匹配字符串(模式串)p在主串s中的位置。一般处理这种问题往往采用简单粗暴的方法——暴力匹配法。所谓暴力匹配法,就是对主串s的每一个字符与要匹配的字符串p的每个字符进行逐一匹配。

i控制主串s的循环,j控制模式串p的循环。返回模式串p在主串s中的位置。

如果当前字符匹配成功时,即s[i]==p[j]时,执行i++和j++操作,进行下一个字符的比较;

如果当前字符匹配失败,即s[i]!=p[j],执行i=i-j+1操作,并将j置为0。通俗来讲就是当匹配失败时,让i回溯到匹配成功时的下一个,让j回复到模式串p的开始位置。

例:主串s="abcdeabcdxabc",模式串p="abcdx"。结果应该返回5。

过程如下:首先,i=0,j=0,很显然s[i]与p[j]相等,则i++,j++,即i=1,j=1,此时s[i]仍然与p[j]相等,则继续执行i++,j++,直到当i=4,j=4时,s[i]与p[j]不再相等,即当前字符匹配失败,此时执行i=i-j+1操作,即此时i回复到s[1]的位置,并把j置为0,因为当i=4时,匹配失败,此时模式串需要从头匹配。然后接下来继续判断s[i]与p[j]是否相等,直到匹配成功或全部遍历完成。可以发现,当i=5时,s[i]与p[j]相等,即从i=5开始,模式串p与主串s开始匹配,直到i=9,j=4时,主串与模式串完全匹配成功,此时返回i-j,即5。

相关代码如下:

int ViolentMatch(char s[], char p[], int pos)
{
    int lens = strlen(s);
    int lenp = strlen(p);
    int i = pos;	//pos为用户自定义的从主串pos位置开始匹配
    int j = 0;
    while (i<lens && j<lenp)
    {
	//如果匹配成功
	if (s[i] == p[j])
	{
	    i++;
	    j++;
	}
	//如果匹配失败
	else
	{
	    i = i - j + 1;
	    j = 0;
	}
    }
    if (j < lenp)
    {
	return -1;
    }
    else
    {
	return i-j;
    }
}

主函数为:

int main()
{
	char* s = "abcdeabcdxabcd";
	char* p = "abcdx";
	int pos = ViolentMatch(s, p, 0);
	//匹配到所有子串的位置
	while (pos != -1)
	{
		printf("%d\n", pos);
		pos = ViolentMatch(s, p, pos+1);
	}
	return 0;
}

除此之外,还有另一种匹配字符串的经典算法——KMP。

此算法可以大大避免重复遍历的情况。操作方法和暴力匹配法的操作方法大致相同:

当主串匹配到i位置,模式串匹配到j位置时,当s[i]==p[j]时,执行i++,j++操作,当s[i]!=p[j]时,i不用回溯到i-j+1的位置,i保持不变,令j=next[j],意味着匹配失败时,模式串p相对于主串s向右移动了j - next[j] 位。而数组next[]是什么呢?数组next[]中各个值的含义是:模式串中当前字符之前的字符串中,有多大长度的相同前缀后缀。要知道next[]数组中存储内容,需要先求出模式串p中各子串的最大前缀后缀公共元素长度,将其存放在数组prefixtable[](前缀匹配表)中。

例:主串s="abcdeabcdxabc",模式串p="abcdx"。

模式串p的最大前缀后缀公共元素长度表为:

模式串中各个子串

前缀

后缀

最大公共元素长度

a

0

ab

a

b

0

abc

a,ab

c,bc

0

abcd

a,ab,abc

d,cd,bcd

0

abcdx

a,ab,abc,abcd

x,dx,cdx,bcdx

 

此操作之后可知道prefixtable数组中的内容是{0,0,0,0,0}。而由于next[]中存放的是当前字符之前的字符串的最大前缀后缀公共元素长度,所以需要将数组prefixtable[]中各值向后移动一位,然后将prefixtable[0]强置为-1。此时的数组则为最终的next[]数组。相关函数操作如下:

//求最大前缀后缀公共元素长度
void PreFixTable(char p[], int lenp, int prefixtable[])
{
	prefixtable[0] = 0;
	int len = 0;
	int i = 1;
	while (i < lenp)
	{
		if (p[i] == p[len])
		{
			len++;
			prefixtable[i] = len;
			i++;
		}
		else
		{
			if (len>0)
			{
				len = prefixtable[len - 1];
			}
			else
			{
				prefixtable[i] = len;
				i++;
			}
		}
	}
}
//后移,首元素置为-1
void MovePreFixTable(int prefixtable[], int n)
{
	for (int i = n - 1; i > 0; i--)
	{
		prefixtable[i] = prefixtable[i - 1];
	}
	prefixtable[0] = -1;
}

关于prefixtable[]数组的求解思想及算法,请访问https://blog.csdn.net/v_july_v/article/details/7041827/

里面有关于KMP算法的更详尽的介绍。

总结一下:要采用KMP算法来进行字符串的匹配,则首先需要得到模式串中各个子串的最大前缀后缀公共元素长度,存在数组prefixtable[]中,其次需要将该数组的各个元素后移一位,将首元素强置为0,之后得到的新数组即next[]数组。然后就可以调用下面的函数进行匹配:

//KMP匹配算法
void KMPMatch(char s[], char p[], int lens, int lenp)
{
	int* prefixtable = (int*)malloc(lenp*sizeof(int));
	PreFixTable(p, lenp, prefixtable);
	MovePreFixTable(prefixtable, lenp);
	int i = 0;
	int j = 0;
	while (i < lens)
	{
		if (j == lenp - 1 && s[i] == p[j])
		{
			printf("%d\n", i - j);
			j = prefixtable[j];
		}
		if (s[i] == p[j])
		{
			i++;
			j++;
		}
		else
		{
			j = prefixtable[j];
			if (j == -1)
			{
				i++;
				j++;
			}
		}
	}
}

主函数:

int main()
{
	char* s = "abcdeabcdxabcd";
	char* p = "abcdx";
	int lens = strlen(s);
	int lenp = strlen(p);
	KMPMatch(s, p, lens, lenp);
	return 0;
}

以上即为我对字符串匹配问题两种方法的理解~~~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值