数据结构——串(详解KMP算法)

1.串的定义:
是由零个或者多个字符组成的有限序列,又叫字符串
不含字符的叫空串
只含空格的串叫做空格串
2.串的抽象数据类型:

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)//返回串得长度
  StrCompare(S,T)//若S>T则返回>0,若S==T,则返回0,否则返回<0;
  Concat(T,S1,S2)//用T返回由S1,S2连接的新串
  SubString(Sub,S,pos,len)//用sub返回串S中第pos个字符长度为len的字串
  Index(S,T,pos)//串S和T存在,T是非空串,若主串S中存在和串T值相同的字串,那么返回它在主串出现的第一个位置,否则返回0
  Replace(S,T,V)//若三个串同时存在,T是非空串,用V替换S串中出现的所有与T相等的且不重叠的子串
  StrInsert(S,pos,T)//若串S和T存在在串S的第pos个字符之前插入串T
  StrDelete(S,pos,len)//若S存在,从S串中删除第pos个字符起长度为len的字串
  endADT                                                             

3.串的顺序存储结构具体实现代码:
别看就一百多行我写了一天
在这里插入图片描述

#include<stdio.h>
#include<bits/stdc++.h>
#include<windows.h>
#define max 100
typedef char string[max+1];
int strassign(string a,char *chars)//生成一个串 
{
	if(strlen(chars)>max)
	{
		printf("串的下标越界!\n");
		return 0;
	}
	a[0]=strlen(chars);
	for(int i=0;i<a[0];i++)
	{
		a[i+1]=chars[i];
	}
	return 1;
}
int strcopy(string t,string s)//把一个串复制给另一个串 (t复制给s) 
{
   s[0]=t[0];
   for(int i=1;i<=s[0];i++)
   {
   	s[i]=t[i];
   }
   return 0; 
}
int clearstring(string s)//将串清空 
{
	s[0]=0;
	memset(s,0,sizeof(s));
	return 1;
}
int stringempty(string s)//判断串是否为空 
{
	if(s[0]==0)
	{
		printf("空\n"); 
		return 1;
	}
	printf("不为空\n"); 
	return 0;
}
int stringlen(string s)//返回串的长度 
{
	return s[0];
 } 
int stringcompare(string s,string t)//比较两个串的大小 
{

	for(int i=1;i<=s[0];i++)
	{
		if(s[i]>t[i])
		{
			//printf("串1比串2大\n");
		    return 1;
		}
		else if(s[i]<t[i])
		{
			// printf("串1比串2小\n");
		     return -1;
		} 
	
		else if(s[i]==t[i]&&i==s[0]&&i<t[0])
		{
		//	printf("串1比串2小\n");
			return -1;	
		} 
		else if(s[i]==t[i]&&i==t[0]&&i<s[0])
		{
		//	printf("串1比串2大\n");
			return 1;
		} 
		else if(s[i]==t[i]&&i==s[0]&&i==t[0]) 
		{
		//	printf("两串相同\n");
			return 0;
		} 
		else if(s[i]==t[i])
		continue;
	} 
} 
int concat(string s,string t1,string t2)//连接两个串 
{
	if(t1[0]+t2[0]>max)
	return 0;
	s[0]=t1[0]+t2[0];
	int cnt=t1[0]+1;
	for(int i=1;i<strlen(t1);i++)
	{
		s[i]=t1[i];
	}
	for(int i=1;i<=t2[0];i++){
		s[cnt++]=t2[i];
	}
	return 1;
}
int substring(string sub,string s,int pos,int len)//获得s的第pos个字符开始长为len的子串长度给sub 
{

	sub[0]=len;
	int cnt=1;
	for(int i=pos;i<pos+len;i++)
	{
		sub[cnt++]=s[i];
	
	}
	return 1;
}
void trav(string s)//遍历串 
{
	if(s[0]==0)
	{
		printf("此串为空\n");
		return ;
	}
	printf("此串的长度为:%d\n",(int)s[0]);
	printf("此串为:");
	for(int i=1;i<=s[0];i++)
	{
		printf("%c",s[i]);
	}
	printf("\n");
}
int index(string s,string t,int pos)//判断s中是否存在子串t 
{
	if(t[0]>s[0])
	return 0;
	int m=s[0];
	int n=t[0];
	int j=pos;
	string subb;
	for(int i=j;i<=m-n+1;i++)
	{
		substring(subb,s,i,n);
		if(stringcompare(subb,t)==0)
	      return i;
	      else
	      continue;
	} 
 	return 0;
} 
int StrInsert(string S, int pos, string T)//在某个位置插入 
{
	  int i;
	  for (i = S[0]; i >= pos; i--)
	  S[i + T[0]] = S[i];
	  for (i = pos; i<pos + T[0]; i++)
	  S[i] = T[i - pos + 1];
	  S[0] = S[0] + T[0];
	  return 0;
}
int stringdelete(string s,int pos,int len)//在s串中从pos位置开始删除长度为len的串 
{
	int  cnt=s[0];
	s[0]=s[0]-len;
    for(int i=pos;i<cnt;i++)
    {
    	s[i]=s[i+len];                         
	}
	return  1;
}
int stringreplace(string s,string t,string v)//把s中的t串换成v串 
{
  	for(int i=1;i<=s[0]-t[0]+1;i++)
	{
		string sub;
	    substring(sub,s,i,t[0]);
		if(stringcompare(sub,t)==0)
		{
			stringdelete(s,i,t[0]);
			StrInsert(s,i,v);
				i=i+t[0]-1;
		}
	}	
}
int main()
{
   char arr1[20];
   char crr1[20];
   char brr1[20];
   scanf("%s%s%s",arr1,brr1,crr1);
   string arr;
   string crr;
   string brr;
   strassign(arr,arr1);
   strassign(brr,brr1);
   strassign(crr,crr1);
    stringreplace(arr,brr,crr);
    trav(arr);  
}

4.串的链式存储结构:
和线性表的链式存储结构表相差不大,总的来说没有顺序结构那么灵活。
5.KMP模式匹配算法:
(1) 首先看一下朴素的匹配方法:
在这里插入图片描述
可以看出来时间复杂度为O(m*n),非常低效。
(2)KMP模式匹配算法(这玩意我看光看书真是让我脑阔疼):
目的:KMP算法的作用是在主串中匹配是否有字串和模式串相同
其优点:是比普通的做法时间复杂度更小
首先看例子:
在这里插入图片描述
朴素算法中,P的第j位失配,默认的把P串后移一位。
但在前一轮的比较中,我们已经知道了P的前(j-1)位与S中间对应的某(j-1)个元素已经匹配成功了。这就意味着,在一轮的尝试匹配中,我们get到了主串的部分内容,利用这些内容,我们可以让P多移几位(我认为这就是KMP算法最根本的东西)
(3)接下来介绍前缀后缀最大公共长度:
前缀:就是包含第一个字符但不包含最后一个字符的连续子串
后缀:就是包含最后一个字符但不包含第一个字符的连续子串
对于串abcab
前缀有
1)a
2)ab
3)abc
4)abca
后缀有
1)b
2)ab
3)cab
4)bcab
对于这个例子最长公共前后缀那必然是ab
(4)next数组:
**next[j]就是第j个元素前j-1个元素所构成的字符串的最大前缀后缀最大公共长度加1;**那么next数组的元素个数就是所匹配串的长度,规定next[1]=0,
若最大前后缀最大公共长度为0,那么数组元素就是1
那么我们手写一个串的next数组:
在这里插入图片描述
(5)
具体实现代码:

#include<stdio.h>
#include<string.h>
#define max 100
typedef char string[max];
int strassign(string a,char *chars)//生成一个串 
{
	if(strlen(chars)>max)
	{
		printf("串的下标越界!\n");
		return 0;
	}
	a[0]=strlen(chars);
	for(int i=0;i<a[0];i++)
	{
		a[i+1]=chars[i];
	}
	return 1;
}
void get_next(string T,int *next)
{
	int i;
	int j;
	i=1;
	j=0;
	next[1]=0;
	while(i<T[0])
	{
		if(j==0||T[i]==T[j])
		{
			next[++i]=++j;
		}
		else
		j=next[j];
	}
}
int index_kmp(string s,string t,int pos) 
{
	int i=pos;
	int j=1;
	int next[20];
	get_next(t,next);
	while(i<=s[0]&&j<=t[0])
	{
		if(j==0||s[i]==t[j])
		{
			i++;
			j++;
		}
		else
		j=next[j];
	}
	if(j>t[0])
	{
		return i-t[0];
	}
	else
	return 0;
}
int main()
{
	char arr1[20];
	char brr1[20];
	printf("请输入主串:\n"); 
	scanf("%s",arr1);
	printf("请输入匹配串:\n");
	scanf("%s",brr1);
	string brr;
	string arr;
	strassign(arr,arr1);
	strassign(brr,brr1);
	printf("此匹配串在主串中的位置:\n");
    printf("%d",index_kmp(arr,brr,1));
	return 0;	
} 

(6)首先讲next数组是如何代码实现:
在已知next前k个元素的值之后求:next[k+1]
1)要求next[k+1] 其中k+1=17
在这里插入图片描述
2)已知next[16]=8,则元素有以下关系:
在这里插入图片描述
3)如果P8=P16,则明显next[17]=8+1=9(是next[k+1]最大的值)
4)如果不相等,又若next[8]=4,则有以下关系
在这里插入图片描述
主要是为了证明:
在这里插入图片描述
5)现在在判断,如果P16=P4则next[17]=4+1=5,否则,在继续递推(这就是get_next里的就j=next[j]的意义)
6)若next[4]=2,则有以下关系
在这里插入图片描述
7)若P16=P2,则next[17]=2+1=3;否则继续取next[2]=1、next[1]=0;遇到0时还没出结果,则递推结束,此时next[17]=1;
8)那么如何使程序找到并返回对应下标
还要提的是i值是没有变小的(这里就叫主串指针没有回朔),只有j的值是一直在变,,终止条件未啥是**j>t[0]**呢,因为一旦有一个匹配到模式串的最后一个字符相等时进入if里边进行++,所有一旦大于t[0]+1=j的时候就是匹配到相同子串的时候。
KMP算法的改进(卧槽还这么。。。。的算法还要改进?我tm:"。。。。"(虎狼之词))在这里插入图片描述
对如主串: aaaabcde
模式串: aaaaax
第一次匹配时模式串的第五个a与主串b不匹配,那么会根据next数组进行重新匹配,结果是会和朴素的算法一样如图:在这里插入图片描述
其实自从模式串的第五个a不与b匹配后,前面的a就不需要进行匹配,这里就出现了低效的一面。
先上代码:

 void get_nextval(string T,int *nextval)
{
	int i;
	int j;
	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])
			nextval[i]=j;
			else
			nextval[i]=nextval[j];//******//
		}
		else
		j=nextval[j];
	}
}

标注星号的地方就是所作出的改进,这个地方的意思是(从开始的主串和模式串举例子):第一次主串的b和模式串的第五个a不匹配那么那一步代表如果第五个a就的位置将会被第五个a的nextal取代,这时候如果取代的那个值和第五个a相等的话,那么就不用进行比较了,肯定不匹配,那么这时候怎么办呢,这时候nextval的值就会等于第五个a的nextval的netval值*这么绕口的说法。。。
那么还拿这张图解释一下:
在这里插入图片描述
这时候假设匹配到16失败(第16位元素的nextval存的值是8,第8位存的是4),那么比较第16位元素的nextval值对应的元素(由图可以看出是第八位)与第16位元素进行对比,如果不相等那么nextval的值就会和此处的next值一样,否则此处(16位)的nextval值就会等于第8位的nextval的值。这里你可能还会有想法,就是你怎么确定第4位(就是第8位nexttval的下标对应得值)的元素和第16一定不等呢?
那么就会有一个递推的思想,就是,我们nextval的值是从前往后推的,那么第8位的netival既然存的是4,那么第8位的元素一定不会和第4位相等,回到第8位与第16位,既然开始说的第8位与第16位的元素相等,那么第16位元素肯定不会和第4位相等。。。
讲到这我就尽力了,理解起来真是让我脑壳疼。。。。
在这里插入图片描述

发布了37 篇原创文章 · 获赞 52 · 访问量 1828

猜你喜欢

转载自blog.csdn.net/qq_45737068/article/details/104388178