数据结构的串的相关知识,朴素的模式匹配,KMP模式匹配

一.串的基本知识

1.串的定义:

串(String)是由零个或多个字符组成的有限序列,又称字符串。
一般记为:
s="a1a2.......an"(a>=0)

注意:其中s是串名,用双引号括起来的字符序列为串值,双引号本身不属于串的内容。ai(1<=i<=n)是一个任意字符,它称为串的元素,是构成串的基本单位,i是它在整个串中的序号;n为串的长度,表示串中所包含的字符个数。

2.空格串:

只包含空格的串,空格串是有长度的,与空串不同。

3.空串:

空串–长度为零的字符串称为空串。可以直接用两个双引号表示“”;

4.字串与主串:

串中任意连续的字符组成的子序列称为该串的子串。包含子串的串为该子串的主串。

5.串的比较:

就是类似于c语言中的strcmp的用法,用ASCII编码来进行比较。

给定两个串:s=”a1a2……an”,t=”b1b2……bm”,当满足以下条件之一时,s < t:

1.n < m,且ai=bi(i=1,2,……,n),其中n代表s字符串的长度,s代表t字符串的长度,前面字符串都相同,但是s字符串的长度比t字符串小也就说明s字符串小于t字符串
2.存在某个k≤min(m,n)这是k>1的时候,使得ai=bi(i=1,2,……,k-1),ak < bk,如果两个字符串从第一个字符就开始不相等也就是说a1 != b1的话就直接只比较a1和b1的大小

6.串的相关数据结构:

ADT 串(string)
Data
    串中元素仅由一个字符组成,相邻元素具有前驱和后继关系。
Operation
    StrAssign(T, *chars):        生成一个其值等于字符串常量chars的串T
    StrCopy(T, S):               串S存在,由串S复制得串T
    ClearString(S):              串S存在,将串清空。
    StringEmpty(S):              若串S为空,返回true,否则返回false。
    StrLength(S):                返回串S的元素个数,即串的长度。
    StrCompare(S, T):            若S>T,返回值>0,若S=T,返回0,若S<T,返回值<0。
    Concat(T, S1, S2):           用T返回由S1和S2联接而成的新串。
    SubString(Sub, S, pos, len): 串S存在,1≤pos≤StrLength(S),
                                 且0≤len≤StrLength(S)-pos+1,用Sub返
                                 回串S的第pos个字符起长度为len的子串。
    Index(S, T, pos):            串S和T存在,T是非空串,1≤pos≤StrLength(S)。
                                 若主串S中存在和串T值相同的子串,则返回它在主串S中
                                 第pos个字符之后第一次出现的位置,否则返回0。
    Replace(S, T, V):            串S、T和V存在,T是非空串。用V替换主串S中出现的所有
    

二.串的存储

1.顺序储存结构:

串的顺序存储结构是用一组地址连续的存储单元来存储串,我们会为串去分配一个固定长度的存储区,一般是用定长的数组来定义的,就是用数组来储存。

注意:我们在C语言当中定义一个字符数组的时候我们需要多加一个长度,因为编译器会自动的帮我们添加一个’\0’,这样方便去确定其的字符串的长度。但这个’\0’不会计入串长。

2.链式存储结构:

用链表存储字符串,每个结点有两个域:一个是数据域(data)和一个指针域(next)。 其中数据域(data)–存储串中的字符。指针域(next)–存放后继结点的地址。

注意:一个节点可以存储一个字符,也可以存放一个字符数组,但是由于字符串的特殊性,用链表存作为字符串的存储方式也不太实用,因此使用较少。

三.朴素的模式匹配算法

就是遍历主串,然后把待匹配字符串与子串进行比对,先把待匹配子串的第一个字母与主串进行匹配,若匹配成功,则两串的坐标依次 ++,匹配不成功时,主串坐标返回到开始匹配时的坐标,待匹配串坐标清零,若待匹配坐标等于待匹配子串长度,则证明匹配成功, 返回匹配完毕主串的第一个坐标,否则返回0
int Index(String S,String T,int pos)       //定义了一个主串,子串,开始查找的位置
 {
      int i=pos;      //主串的查找位置,从该位置以后开始查找是否有字串
     int j=1;          //此处不太理解  为啥其实位置是1,感觉应该是0开始的
     while(i<=S[0]&&j<=T[0])
      {  
	if(S[i]==T[j])//判断两个字符串是否相等
 	{
 		 ++i;         //两个相等时自加1,当判断出字串时,j=T的长度+1
 		 ++j;
 	} 
 	else{
 		 i=i-j+2;    //两个字符不相等时,i-j的相当于返回了当前字串开头的那个字母的前一个字符,然后加2,就相当于从主串的下一个字符作为字串开头
 	 	 j=1;       //这时被匹配的字串回到首位
 	      }
 	 }
   if(j>T[0])
 	 return i-T[0];
   else
	return 0;
 }

但是如果你的主串中匹配时大量每次匹配到最后那一个字符发现不相等,然后重新匹配。。。。如此循环往复,就会浪费大量的时间,因此就有了KMP模式匹配算法。

三.KMP模式匹配

传统朴素模式匹配算法,当主串从i开始进行匹配,当匹配到j位置时,发现不匹配,主串跳回i+1位置,匹配串跳回0位置,这就导致匹配的效率很低,时间复杂度很高。

KMP则当到j位置不匹配时,主串不动,匹配串先通过计算从当前位置的前缀子串和后缀子串的最大匹配串的长度, KMP算法的精髓就是在于求next的过程。

1.如何求next数组

在这里插入图片描述

在这里插入图片描述
注意:1.在我的理解中,前缀后必须有字符,不能整个字符是前缀,同理,后缀前也必须有字符。

例如:如果串是"aaaa",前缀就是"aa",后缀也是"aa",不能是"aaa".
2.对于模式串 T,next[j] 代表了 T 的前 j 个字符组成的子串中,其前缀和后缀的最长公共串的长度+1。

代码如下:

void get_next(char T[], int *next)
{
 
 int i,j;
 i = 1;
 j = 0;
 next[1] = 0;
 while(i < T[0])      //T[0]是子串T的长度
 {       
  //T[i]表示后缀的单个字符
  //T[j]表示前缀的单个字符
  	if( j==0 || T[i] == T[j])    
  	{
  	 ++i;
  	 ++j;
   	next[i] = j;
  	}
  	else
	{
  	 j = next[j];
 	 }
 }
}

整体KMP代码如下:


 
int Index_KMP(char S[], char T[], int pos)
{
 int i = pos;
 int j = 1;
 int next[255];
 get_next(T, next);
 
 while(i <= S[0] && j <= T[0]){
  //相对于朴素算法,增加了一个j==0的判断
  if( j==0 || S[i] == T[j]){
   ++i;
   ++j;
  }else{
   //j回退到合适的位置,i的值不变
   j = next[j];
  }
 }
 if( j>T[0]){
  return i-T[0];
 }else{
  return 0;
 }
}
发布了10 篇原创文章 · 获赞 2 · 访问量 217

猜你喜欢

转载自blog.csdn.net/dfwef24t5/article/details/105441813
今日推荐