数据结构之串(二) 模式匹配算法

tips:本文讲述串的模式匹配算法、KMP模式匹配算法、改进后的KMP模式匹配算法!!!

串的模式匹配算法

子串的定位操作通常称为串的模式匹配

1. 朴素的模式匹配算法

1.1 定义

这是最简单的模式匹配,即暴力匹配算法。
从主串S的第一个字符起,与模式串T的第一个字符比较。若相等,则继续逐个比较后续字符;若不相等,则从主串的下一个字符起,重新和模式串的所有字符比较。
以此类推,直到模式串T中的每个字符依次与主串S中的一个连续的字符序列(子串)相等,则匹配成功,函数返回模式串T首字符在主串S中的位置。否则匹配失败,函数返回值为0.

1.2 原理

在这里插入图片描述
第一次匹配

第二次匹配
第三次匹配

第四次匹配
第五次匹配
第六次循环

1.3 实现代码

/* 返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回值为0。 */
/* 朴素的模式匹配法 */
int Index(String S, String T, int pos)
{
	int i = pos;	/* i用于主串S中当前位置下标值,若pos不为1,则从pos位置开始匹配 */
	int j = 1;				/* j用于子串T中当前位置下标值 */
	while (i <= S[0] && j <= T[0]) /* 若i小于S的长度并且j小于T的长度时,循环继续 */
	{
		if (S[i] == T[j]) 	/* 两字母相等则继续 */
		{
			++i;
			++j;
		}
		else 				/* 指针后退重新开始匹配 */
		{
			i = i - j + 2;		/* i退回到上次匹配首位的下一位 *///i=i-(j-1)+1
			j = 1; 			/* j退回到子串T的首位 */
		}
	}

	if (j > T[0]) //循环结束后,指针j超出T串的范围则成功(j会比T[0]大1)
	{
		return i - T[0];      //返回子串(受字符)在主串中的位置
	}
	else
	{
		return 0;
	}
}

在这里插入图片描述

其中, 每次匹配失败后,指针 i 回退到上一次匹配首位的下一位 的代码:
i = i - j +2 是如何得来的呢?
i = i - j + 2 其实是 i = i - ( j - 1 ) + 1 的简化。
i=i-j+2的解释

1.4 完整代码

#define _CRT_SECURE_NO_WARNINGS 1

#include "string.h"
#include "stdio.h"    
#include "stdlib.h"   

#include "math.h"  
#include "time.h"

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100 /* 存储空间初始分配量 */

typedef int Status;		/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType;	/* ElemType类型根据实际情况而定,这里假设为int */

typedef char String[MAXSIZE + 1]; /*  0号单元存放串的长度 */

/* 生成一个其值等于chars的串T */
Status StrAssign(String T, char* chars)
{
	int i;
	if (strlen(chars) > MAXSIZE)
	{
		return ERROR;
	}
	else
	{
		T[0] = strlen(chars);
		for (i = 1; i <= T[0]; i++)
		{
			T[i] = *(chars + i - 1);
		}
		return OK;
	}
}

Status ClearString(String S)
{
	S[0] = 0;/*  令串长为零 */
	return OK;
}

/*  输出字符串T。 */
void StrPrint(String T)
{
	int i;
	for (i = 1; i <= T[0]; i++)
	{
		printf("%c", T[i]);
	}
	printf("\n");
}


/* 返回串的元素个数 */
int StrLength(String S)
{
	return S[0];
}


/* 返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回值为0。 */
/* 朴素的模式匹配法 */
int Index(String S, String T, int pos)
{
	int i = pos;	/* i用于主串S中当前位置下标值,若pos不为1,则从pos位置开始匹配 */
	int j = 1;				/* j用于子串T中当前位置下标值 */
	while (i <= S[0] && j <= T[0]) /* 若i小于S的长度并且j小于T的长度时,循环继续 */
	{
		if (S[i] == T[j]) 	/* 两字母相等则继续 */
		{
			++i;
			++j;
		}
		else 				/* 指针后退重新开始匹配 */
		{
			i = i - j + 2;		/* i退回到上次匹配首位的下一位 *///i=i-(j-1)+1
			j = 1; 			/* j退回到子串T的首位 */
		}
	}

	if (j > T[0]) //循环结束后,指针j超出T串的范围则成功(j会比T[0]大1)
	{
		return i - T[0];      //返回子串(受字符)在主串中的位置
	}
	else
	{
		return 0;
	}
}


void test01()
{
	String s1, s2;
	StrAssign(s1, "goodgoogle");
	StrAssign(s2, "google");
	//index
	int pos = 1;
	int i_index = Index(s1, s2, pos);
	printf("子串s2在主串s1中第%d个字符之后的位置是(0表示不存在):%d\n", pos, i_index);
	//子串s2在主串s1中第1个字符之后的位置是(0表示不存在):5

}

int main()
{
	test01();

	return 0;
}

2.KMP模式匹配算法

2.1 定义

朴素模式匹配算中指针 i 与 指针 j 会不断回退再进行比较,时间复杂度为O((n-m+1)*m),n为主串长度,m为模式串长度。
为了改进朴素模式匹配算法低效率的缺点,D.E.KnuthJ.H.MorrisV.R.Partt 三位前辈发表了一个模式匹配算法,可以大大避免重复遍历的情况,我们把它称之为 克努特-莫里斯-普拉特算法,简称KMP算法
匹配失败后,指针 i 线性往前递增,指针 j 则进行回溯,时间复杂度为O(n+m)算法效率大大提高。

2.2 原理

2.2.1 前缀 、 后缀、 最大相等前后缀

前缀:指除最后一个字符以外,字符串的所有头部子串 ;
后缀:指除第一个字符以外字符串所有的尾部子串;
最大相等前后缀:字符串前缀和后缀的最长相等前后缀的长度。
例子:子串“ababa”
前缀:{a,ab,aba,abab}
后缀:{a,ba,aba,baba}
最大相等前后缀:{a,aba}

2.2.2 基于朴素模式匹配算法的改进

在上面朴素的模式匹配算法中有两种情况是可以简化步骤的。
情况一:在已匹配的字符中,子串中无最大相等前后缀(子串首位字符与后面其余字符均不相等时)。
在这里插入图片描述
情况二:在已匹配的字符中,子串有最大相等前后缀时。
在这里插入图片描述
可以发现,在之前朴素模式匹配算法中,主串的i值是不断地回溯来完成的,而这种回溯是可以省略的。
即匹配失败后指针 i 不变,指针 j 会跳转到一个特定的值。而这个特定的值 取决于 当前位置前子串的最大相等前后缀
在这里插入图片描述

把模式串T的各个位置的变化定义为一个数组next(存放在数组next中)。

2.2.3 next数组值的推导

next[j] = 当前位置前的子串最大相等前后缀的长度 + 1
在这里插入图片描述
注意:规定 j = 1 时, next [ j ] = 0

2.2.4 示例

主串:“abcddabcab”
模式串:“abcab”

  1. 先推导出模式串串的next数组
    在这里插入图片描述

主串和模式串串从首位字符开始比较,指针i 指向主串S当前比较字符, 指针 j 指向模式串T当前比较位置。
如果当前比较字符相等,i++, j++; 如果不相等,指针i 不变, 指针j 跳转的 next[j] 的位置,继续比较。
并且当指针 j 为0 时,指针 i 与 指针j 同时加1。(即若主串的第i 个位置,与模式串的第一个字符不等时,则应从主串的第 i + 1个位置开始匹配。)

2.3 KMP模式算法的实现

2.3.1 求next数组值
/* 通过计算返回子串T的next数组。 */
//求next数组
void get_next(String T, int* next)
{
	int i = 1;
	int k = 0;
	next[1] = 0;  //同时隐含规定你next[2]=1
	while (i < T[0])  //T[0]为串T的长度
	{
		if (k == 0 || T[i] == T[k])   
		{
			++i;
			++k;		 
 			next[i] = k; //k指向的位置 即最前相等前后缀 中 前缀的后一位
			//当指针i和指针K指向的字符相等时,这两个字符就构成了 一个相等前后缀,长度记为k,
			//经过++i,++k 将k+1存放在下一个i指向的next[]里
			//所以next[k]永远存放 当前位置之前的 最长相等前后缀的长度			  
		}
		else
		{
			k = next[k];   //next[k]中记录着之前最长相等前后缀的长度
			//k = next[k], 从当前的最长相等前后缀的长度(k) 返回 之前的最长相等前后缀的长度(next[k]),继续循环比较,
		}
	}
}

2.3.2 模式匹配(KMP)
/* 返回子串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);	/* 对串T作分析,得到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]; //与朴素的模式匹配算法相比
      	 					//kmp模式匹配算法 匹配失败后 
      	 					// 指针i不变,指针j 回溯到特定的值 next[j]
      	}
	}
	if (j > T[0]) 
	{
		return i-T[0];  //T存在于S中,返回模式串T在主串S中的起始位置
	}
	else 
	{
		return 0;	//不存在
	}
}

在这里插入图片描述

2.4 完整代码

#define _CRT_SECURE_NO_WARNINGS 1

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

#include <math.h> 
#include <time.h>

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100 /* 存储空间初始分配量 */

typedef int Status;		/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType;	/* ElemType类型根据实际情况而定,这里假设为int */

typedef char String[MAXSIZE + 1]; /*  0号单元存放串的长度 */

/* 生成一个其值等于chars的串T */
Status StrAssign(String T, char* chars)
{
	int i;
	if (strlen(chars) > MAXSIZE)
	{
		return ERROR;
	}
	else
	{
		T[0] = strlen(chars);

		for (i = 1; i <= T[0]; i++)
		{
			T[i] = *(chars + i - 1);
		}
		return OK;
	}
}

//清空串
Status ClearString(String S)
{
	S[0] = 0;/*  令串长为零 */
	return OK;
}

/*  输出字符串T。 */
void StrPrint(String T)
{
	int i;
	for (i = 1; i <= T[0]; i++)
	{
		printf("%c", T[i]);
	}
	printf("\n");
}

/*  输出Next数组值。 */
void NextPrint(int next[], int length)
{
	int i;
	for (i = 1; i <= length; i++)
	{
		printf("%d", next[i]);
	}
	printf("\n");
}

/* 返回串的元素个数 */
int StrLength(String S)
{
	return S[0];
}

/* 朴素的模式匹配法 */
int Index(String S, String T, int pos)
{
	int i = pos;	/* i用于主串S中当前位置下标值,若pos不为1,则从pos位置开始匹配 */
	int j = 1;				/* j用于子串T中当前位置下标值 */
	while (i <= S[0] && j <= T[0]) /* 若i小于S的长度并且j小于T的长度时,循环继续 */
	{
		if (S[i] == T[j]) 	/* 两字母相等则继续 */
		{
			++i;
			++j;
		}
		else 				/* 指针后退重新开始匹配 */
		{
			i = i - j + 2;		/* i退回到上次匹配首位的下一位 */
			j = 1; 			/* j退回到子串T的首位 */
		}
	}
	if (j > T[0])
	{
		return i - T[0];
	}
	else
	{
		return 0;
	}
}


//求next数组
void get_next(String T, int* next)
{
	int i = 1;
	int k = 0;
	next[1] = 0;  //同时隐含规定你next[2]=1
	while (i < T[0])  //T[0]为串T的长度
	{
		if (k == 0 || T[i] == T[k])   
		{
			++i;
			++k;		 
 			next[i] = k; //k指向的位置 即最前相等前后缀 中 前缀的后一位
			//当指针i和指针K指向的字符相等时,这两个字符就构成了 一个相等前后缀,长度记为k,
			//经过++i,++k 将k+1存放在下一个i指向的next[]里			  
		}
		else
		{
			k = next[k];   //next[k]中记录着之前最长相等前后缀的长度
			//k = next[k], 从当前的最长相等前后缀的长度(k) 返回 之前的最长相等前后缀的长度(next[k])
		}
	}
}

/* 返回子串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);	/* 对串T作分析,得到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;
	}
		
}

/* 求模式串T的next函数修正值并存入数组nextval */
void get_nextval(String T, int* nextval)
{
	int i, k;
	i = 1;
	k = 0;
	nextval[1] = 0;
	while (i < T[0])  /* 此处T[0]表示串T的长度 */
	{
		if (k == 0 || T[i] == T[k]) 	/* T[i]表示后缀的单个字符,T[k]表示前缀的单个字符 */
		{
			++i;
			++k;
			if (T[i] != T[k])
			{
				                /* 若当前字符与前缀字符不同 */
				nextval[i] = k;	/* 则当前的k为nextval在i位置的值 */
			}	
			else
			{
				nextval[i] = nextval[k];	/* 如果与前缀字符相同,则将前缀字符的 */
										    /* nextval值赋值给nextval在i位置的值 */
			}			
		}
		else
		{
			k = nextval[k];			/* 若字符不相同,则k值回溯 */
		}
			
	}
}



int Index_KMP1(String S, String T, int pos)
{
	int i = pos;		/* i用于主串S中当前位置下标值,若pos不为1,则从pos位置开始匹配 */
	int j = 1;			/* j用于子串T中当前位置下标值 */
	int next[255];		/* 定义一next数组 */
	get_nextval(T, next);	/* 对串T作分析,得到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;
}


void test01()
{	
	String s, t;
	//主串
	StrAssign(s, "abcddabcab");
	StrPrint(s);   //abcddabcab
	
	//子串
	StrAssign(t, "abcab");
	StrPrint(t);  //abcab

	//子串next
	int i = StrLength(t);  //子串的长度
	int* p = (int*)malloc((i + 1) * sizeof(int)); //开辟存放子串next值的数组空间,长度与子串长度相同
	get_next(t, p);
	//next值
	NextPrint(p, StrLength(t));  //01112  

	//index_kmp
	int i_index = Index_KMP(s, t, 1);
	printf("%d", i_index);  //6
}

void test02()
{
	String s, t;
	//主串
	StrAssign(s, "abcabaaaab");
	StrPrint(s);   //abcddabcab

	//子串
	StrAssign(t, "abaaaab");
	StrPrint(t);  //abaaaab

	//子串nextval
	int i = StrLength(t);  //子串的长度
	int* p = (int*)malloc((i + 1) * sizeof(int)); //开辟存放子串next值的数组空间,长度与子串长度相同
	get_nextval(t, p);
	//next值
	NextPrint(p, StrLength(t));  //0102221

	//index_kmp1
	int i_index = Index_KMP1(s, t, 1);
	printf("%d", i_index);  //4

}

int main()
{
	//test01();

	test02();

	return 0;
}

3. KMP模式匹配算法的改进

3.1 KMP算法的缺陷

例如:
主串S = “aaaabcde”
模式串T = “aaaaax”

模式串T的next值
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
由于串T的第二、三、四、五位置的字符都与 首位字符“a”相等,那么可以跳过上面的2、3、4、5步骤,即直接用首位next[1] 的值去取代与它相等的字符后续next[j]的值。

3.2 nextval数组的推导

/* 求模式串T的next函数修正值并存入数组nextval */
void get_nextval(String T, int* nextval)
{
	int i, k;
	i = 1;
	k = 0;
	nextval[1] = 0;
	while (i < T[0])  /* 此处T[0]表示串T的长度 */
	{
		if (k == 0 || T[i] == T[k]) 	/* T[i]表示后缀的单个字符,T[k]表示前缀的单个字符 */
		{
			++i;
			++k;
			if (T[i] != T[k])
			{
				                /* 若当前字符与前缀字符不同 */
				nextval[i] = k;	/* 则当前的k为nextval在i位置的值 */
			}	
			else
			{
				nextval[i] = nextval[k];	/* 如果与前缀字符相同,则将前缀字符的 */
										    /* nextval值赋值给nextval在i位置的值 */
			}			
		}
		else
		{
			k = nextval[k];			/* 若字符不相同,则k值回溯 */
		}
			
	}
}

例如:
模式串T“ababaaaba”
先算出next数组的值,在分别进行判断。
在这里插入图片描述

  1. 当 j = 1 时,nextval[1] = 0;
  2. 当 j = 2 时, 第2位的字符“b”的next值是1,而其指向的next ,就是第1位“a”,它们不相等, 所以nextval[2] = next[2] =1;(k = next[k])
  3. 当 j = 3 时,第3为字符“a”的next值是1 , 所以与第1位字符“a”比较,相等,所以nextval[3] = nextval[1] = 0 ;(nextval[i] = nextval[k])
  4. 当 j = 4 时,第4位的字符“b”next值为2, 所以与第2位字符“b”相比较,相等,所以nextval[4] = nextval[2] = 1;
  5. 当 j = 5 时, next值为3, 第5个字符“a”与第3个字符“a”比较,相等,nextval[5] = nextval[3] = 0;
  6. 当 j = 6 时, next值为4, 第6个字符“a”与第4个字符“b”比较,不相等,nextval[6] = 4;
  7. 当 j = 7 时, next值为2, 第7个字符“a”与第2个字符“b”比较,不相等,nextval[7] = 2;
  8. 当 j = 8 时,next值为2, 第8个字符“b”与第2个字符“b”比较,相等,nextval[8] = nextval[2] = 1;
  9. 当 j = 9 时,next值为3, 第9个字符“a”与第3个字符“a”比较,相等,nextval[9] = nextval[3] = 1;

3.3 改进后的KMP模式匹配算法

与改进前的算法一样

int Index_KMP1(String S, String T, int pos)
{
	int i = pos;		/* i用于主串S中当前位置下标值,若pos不为1,则从pos位置开始匹配 */
	int j = 1;			/* j用于子串T中当前位置下标值 */
	int next[255];		/* 定义一next数组 */
	get_nextval(T, next);	/* 对串T作分析,得到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;
}

3.4 完整代码

#define _CRT_SECURE_NO_WARNINGS 1

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

#include <math.h> 
#include <time.h>

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100 /* 存储空间初始分配量 */

typedef int Status;		/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType;	/* ElemType类型根据实际情况而定,这里假设为int */

typedef char String[MAXSIZE + 1]; /*  0号单元存放串的长度 */

/* 生成一个其值等于chars的串T */
Status StrAssign(String T, char* chars)
{
	int i;
	if (strlen(chars) > MAXSIZE)
	{
		return ERROR;
	}
	else
	{
		T[0] = strlen(chars);

		for (i = 1; i <= T[0]; i++)
		{
			T[i] = *(chars + i - 1);
		}
		return OK;
	}
}

//清空串
Status ClearString(String S)
{
	S[0] = 0;/*  令串长为零 */
	return OK;
}

/*  输出字符串T。 */
void StrPrint(String T)
{
	int i;
	for (i = 1; i <= T[0]; i++)
	{
		printf("%c", T[i]);
	}
	printf("\n");
}

/*  输出Next数组值。 */
void NextPrint(int next[], int length)
{
	int i;
	for (i = 1; i <= length; i++)
	{
		printf("%d", next[i]);
	}
	printf("\n");
}

/* 返回串的元素个数 */
int StrLength(String S)
{
	return S[0];
}

/* 朴素的模式匹配法 */
int Index(String S, String T, int pos)
{
	int i = pos;	/* i用于主串S中当前位置下标值,若pos不为1,则从pos位置开始匹配 */
	int j = 1;				/* j用于子串T中当前位置下标值 */
	while (i <= S[0] && j <= T[0]) /* 若i小于S的长度并且j小于T的长度时,循环继续 */
	{
		if (S[i] == T[j]) 	/* 两字母相等则继续 */
		{
			++i;
			++j;
		}
		else 				/* 指针后退重新开始匹配 */
		{
			i = i - j + 2;		/* i退回到上次匹配首位的下一位 */
			j = 1; 			/* j退回到子串T的首位 */
		}
	}
	if (j > T[0])
	{
		return i - T[0];
	}
	else
	{
		return 0;
	}
}


//求next数组
void get_next(String T, int* next)
{
	int i = 1;
	int k = 0;
	next[1] = 0;  //同时隐含规定你next[2]=1
	while (i < T[0])  //T[0]为串T的长度
	{
		if (k == 0 || T[i] == T[k])   
		{
			++i;
			++k;		 
 			next[i] = k; //k指向的位置 即最前相等前后缀 中 前缀的后一位
			//当指针i和指针K指向的字符相等时,这两个字符就构成了 一个相等前后缀,长度记为k,
			//经过++i,++k 将k+1存放在下一个i指向的next[]里			  
		}
		else
		{
			k = next[k];   //next[k]中记录着之前最长相等前后缀的长度
			//k = next[k], 从当前的最长相等前后缀的长度(k) 返回 之前的最长相等前后缀的长度(next[k])
		}
	}
}

/* 返回子串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);	/* 对串T作分析,得到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;
	}
		
}

/* 求模式串T的next函数修正值并存入数组nextval */
void get_nextval(String T, int* nextval)
{
	int i, k;
	i = 1;
	k = 0;
	nextval[1] = 0;
	while (i < T[0])  /* 此处T[0]表示串T的长度 */
	{
		if (k == 0 || T[i] == T[k]) 	/* T[i]表示后缀的单个字符,T[k]表示前缀的单个字符 */
		{
			++i;
			++k;
			if (T[i] != T[k])
			{
				                /* 若当前字符与前缀字符不同 */
				nextval[i] = k;	/* 则当前的k为nextval在i位置的值 */
			}	
			else
			{
				nextval[i] = nextval[k];	/* 如果与前缀字符相同,则将前缀字符的 */
										    /* nextval值赋值给nextval在i位置的值 */
			}			
		}
		else
		{
			k = nextval[k];			/* 若字符不相同,则k值回溯 */
		}
			
	}
}



int Index_KMP1(String S, String T, int pos)
{
	int i = pos;		/* i用于主串S中当前位置下标值,若pos不为1,则从pos位置开始匹配 */
	int j = 1;			/* j用于子串T中当前位置下标值 */
	int next[255];		/* 定义一next数组 */
	get_nextval(T, next);	/* 对串T作分析,得到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;
}



void test01()
{	
	String s, t;
	//主串
	StrAssign(s, "abcddabcab");
	StrPrint(s);   //abcddabcab
	
	//子串
	StrAssign(t, "abcab");
	StrPrint(t);  //abcab

	//子串next
	int i = StrLength(t);  //子串的长度
	int* p = (int*)malloc((i + 1) * sizeof(int)); //开辟存放子串next值的数组空间,长度与子串长度相同
	get_next(t, p);
	//next值
	NextPrint(p, StrLength(t));  //01112  

	//index_kmp
	int i_index = Index_KMP(s, t, 1);
	printf("%d", i_index);  //6
}

void test02()
{
	String s, t;
	//主串
	StrAssign(s, "abcabaaaab");
	StrPrint(s);   //abcddabcab

	//子串
	StrAssign(t, "abaaaab");
	StrPrint(t);  //abaaaab

	//子串nextval
	int i = StrLength(t);  //子串的长度
	int* p = (int*)malloc((i + 1) * sizeof(int)); //开辟存放子串next值的数组空间,长度与子串长度相同
	get_nextval(t, p);
	//next值
	NextPrint(p, StrLength(t));  //0102221

	//index_kmp1
	int i_index = Index_KMP1(s, t, 1);
	printf("%d", i_index);  //4

}

int main()
{
	//test01();

	test02();

	return 0;
}

END

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值