数据结构与算法 - 串 - 从主串中查找子串 - KMP模式匹配

1、定义

串是由零个或多个字符组成的有限序列,又名字符串

2、抽象数据类型

  • Data 串中元素仅由一个字符组成,相邻元素具有前驱和后继关系
  • Operation
  1. StrAssign( T, *chars )  生成一个其值等于字符串常量chars的串T
  2. StrCopy( T, S )  串S存在,由串S复制得串T
  3. ClearString( S )  串S存在,将串清空
  4. StringEmpty( S )  若串为空 返回true 否则返回false
  5. StrLength( S )  返回串S的元素个数,即串的长度
  6. StrCompare( S, T )  若S>T,返回>0, 若S=T,返回0,若S<T 返回<0
  7. Concat( T, S1, S2 )  用T返回S1和S2连接而成的新串
  8. SubString( Sub, S, pos, len )  串存在 1≤pos≤StrLength(S), 且 0≤len≤StrLength(S)-pos+1,用sub返回串S的第pos个字符起长度为len的子串
  9. Index( S, T, pos )  串S和T存在,T是非空串,1≤pos≤StrLength(S), 若主串S种存在和串T值相同的子串,则返回它在主串S中 第pos个字符之后第一次出现的位置,否则返回0
  10. Replace( S,T,V )  串S T V存在, T是非空串。用V替换主串S中出现的所有与T相等的不重叠的子串
  11. StrInsert( S, pos, T )  串S和T存在 1≤pos≤StrLength(S)+1  在串S的第pos个字符之前插入串T
  12. StrDelete( S, pos, len )  串存在 1≤pos≤StrLength(S)+1 从串S种删除第pos个字符起长度为len的子串

3、查找子串-(1)-使用自带API

//T为非空串。若主串S中第pos个字符之后存在与T相等的子串,则返回第一个这样的子串在S中的位置,否则返回0
int Index( string S, string T, int pos)
{
	if (pos <= 0)
	{
		return 0;
	}
	int s_len = StrLength(S);
	int t_len = StrLength(T);
	
	int i = pos;
	string sub;

	while(s_len - i + 1 >= t_len)
	{
		SubString(sub, S, i);
		if (StrCompare(sub, T) != 0)
		{
			++i;
		}
		else
		{
			return i;
		}
	}
	return 0;
}

4、串的存储结构

5、朴素模式匹配算法

6、查找子串-(2) 朴素模式匹配算法

//手写查找子串操作
//前提 主串S和要匹配的子串T的长度存在数组的第一个位置 即 S[0] T[0]中
//返回子串T在主串S中第pos个字符之后的位置,若不存在 则返回0
//T非空 1≤pos≤StrLength(S)
int Index(string S, string T, int pos)
{
	int i = pos;
	int j = 1;
	while (i <= S[0] && j <= T[0])
	{
		//当主串中余下的长度 小于子串时,无需再进行比较
		if (S[0]-i+1 < T[0]-j+1)
		{
			return 0;
		}

		if (S[i] == T[j])
		{
			++i;
			++j;
		}
		else
		{
			i = i-j+2;//i退回上次匹配首位的下一位
			j = 1;
		}		
	}

	if (j > T[0])
	{
		return i-j+1;
	} 
	else
	{
		return 0;
	}
}

7、查找子串-(3) KMP模式匹配算法

      1)、相对于朴素模式 可以省略的操作

            ①若子串中首字符 与其后面的字符均不等,且已于主串比较过并且相等,那么这些字符的匹配判断都可省略,下次判断将从子串的首位与 主串与子串最后一个相等的下个位置开始判断

        

            ②对于在子串中有与首字符开始依次相等的字符集,可以省略其匹配判断

            子串的4、5位与首字符相等,所以主串中对应的第4、5位不需再次与子串中第1、2位相比较;直接对子串j=3与主串i=6进行比较

      2)、KMP模式匹配算法 思想

      3)、子串各个位置的 j 值变化函数

            ①next数组定义

            ②解释

            中间Max后的式子表示串的前后相似度,P 1 : 子串的首字符;P k-1 : 子串中第k-1个字符;P j-k+1 : 子串的第 j-k+1个字符;P j-1 : 与主串相等的子串中的最后一个位置。

            总结: j = 与主串依次相等的子串 部分中,前缀与后缀相等的部分中,前缀相似字符的 下一个位置。

            注:j从1开始,最大为子串的长度

                        j=1时,子串未与主串开始比较,表示下次需比较j=1位置的字符

                        j=2时,子串的第一个位置字符与主串第一个字符相比较,且相等

                        j=3时,子串的第3个位置字符之前的所有字符 均与主串相比,且相等

      4)、next数组值推导

            ①

            

            

            ②

            

            

            ③

            

            

            ④

            

            

结论:如果前后缀一个字符相等,k值是2,两个字符相等 k值是3,n个相等k值就是n+1

      5)、代码

//KMP模式匹配算法

//通过计算返回子串T的next数组,计算出当前要匹配的串T的next数组
void get_next(string T, int *next)
{
	int i=1; //i 表示子串的下标从1开始依次增加,T[i]用于表示后缀字符
	int j=0; //j 表示与后缀相等的个数/下标,T[j]用于表示前缀字符

	next[1] = 0;

	while (i < T[0]) //此次T[0]表示串T的长度,假设串种第1个位置存储长度
	{
		if (j == 0 || T[i] == T[j])//T[i]表示后缀的单个字符;T[j]表示前缀的单个字符
		{
			++i;
			++j;
			next[i] = j;
		} 
		else
		{
			j = next[j]; //若字符不相同,则j值回溯
		}
	}
}

//返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回0
//T非空,1≤pos≤StrLength(S)
int Index_KMP(string S, string T, int pos)
{
	int i = pos; //i用于主串S当前位置下标值,若pos不为1,则从pos位置开始匹配
	int j = 1; //j用于子串T中当前位置下标
	int next[255]; //定义next数组,用于存储子串下一位置的下标

	get_next(T, next); //对子串分析,得出next数组

	while (i <= S[0] && j <= T[0]) //若i小于S的长度且j小于T的长度时,循环继续
	{
		if (j == 0 || S[i] == T[j]) //两字符想定则继续,比朴素算法增加了j=0判断
		{
			++i;
			++j;
		} 
		else //指针后退重新开始匹配
		{
			j = next[j]; //j退回合适的位置,i值不变
		}
	}
	if (j > T[0])
	{
		return i - T[0];
	}
	else
	{
		return 0;
	}
}

      5)、图视

8、KMP模式匹配算法-改进

    根据上图,我们发现,当中②③④⑤步骤,其实是多余的判断。由于T串的第二、三、四、五位置的字符都与首位'a'相等,那么可以用首位next[1]的值去取代与它相等的字符后续next[j]的值。

    我们用数组为nextval,取代next数组

//KMP算法 改良
//更新子串nextval数组
void get_nextval(string T, int *nextval)
{
	int i=1, j=0;
	nextval[1] = 0;

	while (i < T[0])
	{
		if (j == 0 || T[i] == T[j])
		{
			++i;
			++j;

			if (T[i] != T[j]) 
			{//若当前字符与前缀字符不同 则当前的j为nextval在i位置的值
				nextval[i] = j;
			} 
			else
			{//如果与前缀字符相同,则前缀字符的nextval值赋值给nextval在i位置的值
				nextval[i] =nextval[j];
			}
		} 
		else
		{
			j = nextval[j];
		}
	}
}

//实际匹配算法不变,与Index_KMP相同,只需将next换为nextval

    nextval值推导

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值