第五章 串

串的定义

串(String)是由零个或多个字符组成的有限序列(所谓序列就是相邻字符之间具有前驱和后继的关系),又名叫字符串
一般记作string-’a1a2a3…an‘(n>=0),其中string是串的名称,引号包裹的字符序列是串的值,注意引号不属于串的内容。
串中的字符数目n称为串的长度,定义中谈到有限是指长度n是一个有限的数值。零个字符的串称为空串

串的比较

给定两个串s1=“a1a2a3a4…an”,s2=“b1b2b3b4…bn”,当满足一下条件之一时,s1<s2
1、n<m,且ai=bi(i=1,2,3…n)
例如:当s1=“hap”,s2=“happy”,就有s1<s2.
2、存在某个k<=min(m,n),使得ai=bi(i=1,2,3…k-1),ak<bk
例如:s1=“happen”,s2=“happy”,e<y 所有 s1<s2.

串的抽象数据类型

对于线性表来说,线性表更多的关注单个元素的操作,比如插入、删除、查找等,但串中更多的是查找子字符串位置、得到指定位置子字符串、替换子字符串等

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中出现的所有与T相等的不重叠的字串。
	StrInsert(S,pos,T):串S和T存在,1<=StrLength(S)+1.在串S的第pos个字符之间插入串T。
	StrDelete(S,pos,len):串S存在,1<=pos<=StrLength(S)-len+1.从串S中删除第pos个字符起长度为len的子窜
endADT		

例如index的操作:

//T为非空串,若主串S中第pos个字符之后存在与T相等的字串,则返回第一个这样的字串在S中的位置,否则返回0
int Index(String S,String T,int pos){
	int n,m,i;
	String sub;
	if(pos>0){
		n=StrLength(S);
		m=StrLength(T);
		i=pos;
		while(i<=n-m+1){
			SubString(sub,S,i,m);  //取主串第i个位置
			if(StrCompare(sub,T)!=0)
				++i;
			else					//如果两串相等
				return i;			//返回I值
		}
	}
	return 0; //无字串与T相等,返回0
}
朴素的模式匹配算法

字串的定位操作通常称为串的模式匹配
简单的说,就是对主串的每个字符作为字串开头,与要匹配的字符串进行匹配。对主串做大循环,每个字符开头做T的长度的小循环,直到匹配成功或全部遍历完成为止。
假设T[0]与S[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[i] == T[i]){
			++i;
			++j;
		}else{
			i=i-j+2;    	//i退回到上次匹配首位的下一位。
			j=1;  		    //j退回到字串T的首位;
		}
	}
	if(j>T[0])
		return i-T[0];
	else
		return 0;	
}
KMP模式匹配算法

D.E.Knuth、J.H.Morris、V.R.Pratt(其中Knuth、Pratt共同研究,Morris独立研究)发表的一个模式匹配算法,可以大大避免重复遍历的情况,我们把它称之为克努特-莫里斯-普拉特算法,简称KMP算法。

KMP模式匹配算法原理

假设现在有==字符串S=“abcdefgab”,字符串T=“abcdex”.==如果用之间的朴素算法的话,前5个字母,两个串完全相等,直到第6个字母,"f"与“x”不等,如下图:
在这里插入图片描述
接下来,按照朴素模式匹配算法,应该是上图的2、3、4、5、6流程。即主串S中当i=2、3、4、5、6时,首字符与字串T的首字符均不等。仔细观察发现,对于要匹配的字串T来说,“abcdex”首字母“a”与后面的串“bcdex”中任意一个字符都不相等。也就是说,既然"a"不与自己后面任意一个字母相等,那么对于上图的1来说,前5位与主串S相同。那么串T的"a"与串S的2-5位字符也不可能相等。所以其实上图的2-5步的比较都是多余的。
注意这是理解KMP字符串算法的关键,如果我们知道T串中首字符“a”与“T”中后面的字符均不相等。而T串的第二位“b”与S串中的第二位“b”在图1中判断已经是相等的,那么就意味这,T串中首字符“a”与S串中第二位“b”是不需要判断也知道他们是不可能相等的了,这样图2-5其实都是可以省略的。
那么有人就会问,如果T串后面也含有首字符"a"怎么办?
我们来看下一个例子:假设S=“abcababca”,T=“abcabx”.
在这里插入图片描述
根据图1所知,串S与串T前5位均相等。根据刚才经验判断,T的"a"与T的“b”、“c”均不相等,所以图2、3可以省略。因为串T的首位“a”与第四位“a”相等,第二位“b”与第五位“b”相等。而在图1时,第四位的“a”与第五位的“b”已经与主串“S”中想对应位置比较过,是相等的,因此可以判定,T的首字符“a”、第二位字符“b”与S的第四位字符和第五位字符也不需要比较了。肯定是相等的。所以图4、5也是可以省略的。也就是说对于在字串中有与首字符相等的字符串,也是可以省略一部分不必要的判断步骤。

对比这两个例子,我们发现在图1时,我们的i值,也就是主串当前位置的下标是6,图2、3、4、5 i值是2、3、4、5到了图6,i值才又回到了6.即我们在朴素的模式匹配算法中,主串的i值是不断地回溯来完成的。而我们分析发现,这种回溯其实可以省略。KMP算法就是省略这些不必要的回溯。
比如第一张图中,由于T=“abcdex”,当中没有任何重复的字符串,所以j就由6变成了1.而第二张图中,由于T=“abcabx”,前缀“ab”与最后“x”之前串的后缀“ab”是相等的,因此j就由6变成了3.所以,我们可以得出规律,j值得多少取决于当前字符之前得串的前后缀的相似度。
我们把T串各个位置的J值的变化定义为一个数组next,那么next的长度就是T串的长度。于是我们可以得到下面的函数定义:
在这里插入图片描述
next数组值推导:
T=“ababaaaba”
在这里插入图片描述
1、当j=1时,next[1]=0;
2、当j=2时,next[2]=0;
3、当j=3时,next[3]=0;
4、当j=4时,j由1到j-1的串时"aba",前缀字符“a”与后缀字符“a”相等,next[4]=2;
5、当j=5时,j由1到j-1的串是“abab”,由于前缀字符“ab”与后缀字符“ab”相等,所以next[5]=3;
6、当j=6时,j由1到j-1的串是“ababa”,由于前缀字符“aba”与后缀字符"aba"相等,所以next[6]=4;
7、当j=7时,j由1到j-1的串是“ababa”,由于前缀字符“ab”与后缀“aa”并不相等,只有"a"相等,所以next[7]=2;
8、当j=8时,j由1到j-1的串是“ababaaa”,只有“a”相等,所以next[8]=2;
9、当j=9时,j由j-1的串是“ababaaab”由于前缀字符"ab"与后缀“ab”相等,所以 next[9]=3;
KMP模式匹配算法实现:

//计算返回字串T的next数组
void get_next(String T,int *next){
	int i,j;
	i=1;
	j=0;
	next[1]=0;
	while(i<T[0]){        //此处T[0]为串的长度    
		if(j==0 || T[i]==T[j]) {
			++i;
			++j;
			next[i] = j;
		}else{
			j= next[j];  //若字符不相同,j值回溯
		}
	}
}
//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;	
}

KMP算法改进

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

nextval数组值推导:
在这里插入图片描述
1、当j=1时,nextval[1]=0;
2、当j=2时,因第二位字符“b”的next值是1,而第一位就是“a”,他们不相等,所以nextval[2]=next[2]=1,维持原值;
3、当j=3时,因为第三位字符“a”的next值为1,所以与第一位的“a”比较得知它们相等,所以nextval[3]=nextval[1]=0;
在这里插入图片描述

4、当j=4时,第四位得字符“b”next值为2,所以与第二位的“b”相比较得到结果是相等,因此nextval[4]=nextval[2]=1;
5、当j=5时,next值为3,第五个字符“a”与第三个字符“a”相等,因此nextval[5]=nextval[3]=0;
6、当j=6时,next值为4,第六个字符“a”与第四个字符“b”不相等,因此nextval[6]=4;
7、当j=7时,next值为2,第七个字符“a”与第二个字符“b”不相等,因此nextval[7]=2;
8、当j=8时,next值为2,第八个字符“b”与第二个字符“b”相等,因此nextval[8]=nextval[2]=1;
9、当j=9时,next值为3,第九个字符“a”与第三个字符“a”相等,因此nextval[9]=nextval[3]=0;

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值