【数据结构与算法】串匹配@KMP算法

串:都是由ASCII码组成的,长度无论。
串的表示方法:顺序存储结构(数组),非线性存储结构(链表)。

还有一种存储方式,(可以说是链表的数组的结合体):
一个节点里有一个八个字节的空间来存放字符和一个四个字节的空间来存放指针,如下:
在这里插入图片描述下面讨论它的增删改查
假如我要删除某个字符
在这里插入图片描述
在这里插入图片描述那么就需要把后面的所有字符向前移,这样虽然可以删除,但是时间复杂度太高。有一种做法,是把这个位置的字符,改成一个无效字符,这样可以极大的降低时间复杂度。

如果要删除不止一个字符的话,如下:

在这里插入图片描述那么要删除如上的第二个节点中的六个字符,就需要把其中的六个字符变成无效字符,但是我们可以比较删除的个数和剩余的个数,像这样删除了6个字符,我们可以把删除的往后挪,把有效字符往前挪,把它与前一个节点比较,如果可以把它移动到前一个节点处存储的话,那么可以直接挪过去,把只针对指向修改,直接把当前节点删除。这个就有些类似于计算机的磁盘整理,碎片扫描。因为一个逻辑完整的文件可能会分到若干个根本不连续的扇区上去。
对于插入字符,我们可以直接找到需要插入的位置,然后再申请一个节点进行插入,最后需要修改一下指针的值就ok!
在这里插入图片描述

串的app功能:1.行编辑(最多只能编辑一行字符,即,不能存在回车或换行),2.全屏幕编辑。

这里要实现一个功能:字符串匹配
在一个源字符串中,查找一个目标字符串(子字符串,简称:子串),第一次出现的位置。例如:
This is a good sister. 中 is 出现在第3个位置。
一般方法:

int searchSubstring (const char *str,const char *substr)  {
		strLen = strlen(str);
		subLen = strlen(substr);
		for (i = 0;strlen - i >= subLen;i++) {
				for (j = 0;substr[j];j++) {
					 if(str[i + j] != substr[j]) {
							  break;
					  }
			    }
				 if (substr[j] == 0) {
						 reutnr i;
	    		 } 		
		}		
}	

上述方法虽然可以找到,但是时间复杂度太高,下面介绍一种时间复杂度很低的方法。
KMP算法
字符串:aadaadaadaadaadaaaadaadaad
需要找的子串为:aadaadaaaadaa

aadaadaaaadaa (子串)
01234567890123 (下标)
其中:
下标为0的字符a,前面有0个字符适配
下标为1的字符a,前面有0个字符适配
下标为2的字符d,前面有1个字符适配,为:a
下标为3的字符a,前面有0个字符适配,为:a
下标为4的字符a,前面有1个字符适配
下标为5的字符d,前面有2个字符适配,为:aa
下标为6的字符a,前面有3个字符适配,为:aad
下标为7的字符a,前面有4个字符适配,为:aada
下标为8的字符a,前面有5个字符适配,为:aadaa
下标为9的字符a,前面有2个字符适配,为:aa
下标为10的字符d,前面有2个字符适配,为:aa
下标为11的字符a,前面有3个字符适配,为:aad
下标为12的字符a,前面有4个字符适配,为:aada
适配的意思是,一方面从当前字符的前一个开始算起,另一方面从开头算起,两个字符串相等且最长,那么这个字符串的长度就可以作为适配长度。
上述的适配字符个数是我们用肉眼观察出来的,变化成代码如下:
aadaadaaaadaa (子串)
0010123452234 (对应每个字符的适配长度)

01234567890123  i
aadaadaaaadaa  
00			    j 
i:2/3/4/5/6/7/8/9/10/11/12/113
j:1/0/0/1/2/3/4/5/2/1/2/1/2/3/4			
		substr[i - 1] == substr[j]
		if (substr[i - 1] != substr[j]) {   |   		左边的条件可以化简为:
			if (j == 0) {					|		substr[i - 1] != substr[j]&& j == 0  				
				next[i++] = (j == 0? j:++j);|			|| substr[i - 1] == substr[j]
			} else {                        |  
				j = next[j];				|		因为!A && B || A <=> A || B
			}|                              |
		} else {				            |		所以左边的代码可以优化为:
			next[i++] = (j == 0? j:++j);	|		if (substr[i-1] == substr[j] || j == 0)
		}									|			next[i++] = (substr[i-1] != substr[j] ? j : ++j);  
	}										|		else
/*next数组用来存放每个字符所对应的适配长度*/   |		j = next[j]; 
最终的代码为:
 i = 2;
 j = 0;
while (str[i]) {
	if (str[i - 1] == str[j] || i == 0) {
		next[i++] =  (str[i - 1] != str[j] ? j : ++j);  
	} else {
		j = next[j];
	}
}

上述代码确立了每个字符的适配个数,并将适配个数存入next数组中,形成了next数组。

接下来就只需要进行比较!

while (str[i] && sub[j]) {
		if (str[i] != sub[j]) {
			if (j == 0) {
				++i;
			} else {
				j = next[j];
			}
		} else {
			++i;
			++j;
		}
		if (sub[j] == 0) {
			free(next);
			return i - j;
		}
	}
	free(next);

如果在循环里面return ,则说明在源串中找到了子串,并且返回这个位置,如果不在循环中return,那就意味着在源串中找到最后也没有找到子串。

上述代码是整个代码的最核心部分。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值