数据结构笔记---串

四、串

1. 串的定义和实现

1.1 串的定义

  • 串(String):由0个或者多个字符组成的有限序列。一般记为:S=‘a1a2…an’ (n>=0)
  • 串名:S
  • 串值:引号内部内容
  • 串长:串内字符个数
  • 空串:串长等于0的串
  • 子串:串中任意个连续的字符组成的子序列
  • 主串:包含主串的串
  • 字符在主串中的位置:字符在串中第一次出现的位置
  • 子串在主串中的位置:子串的第一个字符在主串中的位置
  • 空格串:由空格组成的串,每个空格占1B

串数据对象限定为字符集(中文字符、英文字符、数字字符、标点字符等等)

1.2 串的存储结构

1. 定长顺序存储表示

存储结构:

  • 一组地址连续的存储单元存储串值的字符序列,即使用定长数组存储。
  • ch[MAXLEN]:每个分量存储一个字符。
  • int length:串的实际长度。
typedef struct {
	char ch[MAXLEN];
	int length;
}SString;
2. 堆分配存储表示

存储结构:

  • 一组地址连续的存储单元存储串值的字符序列,但是存储空间是动态分配得到的(在堆区)。
  • char ch:按照串长分配存储区域,ch指向串的基地址。
  • int length:串的实际长度。
typedef struct {
	char *ch;
	int length;
}HString;

顺序串的实现细节:

  • 方案一:使用int length的方法存储长度
    在这里插入图片描述
    特点:占用额外的空间,但是清楚明了,能够表示的串长范围大。

  • 方案二:使用ch[0]存储length
    在这里插入图片描述
    特点:字符位序与数组下标相同,但是ch[0]只能存储1B数据,所以串长最多为256.

  • 方案三:没有length变量,以 /0 表示结尾
    在这里插入图片描述
    特点:无法直接的表示串长。

  • 方案四:不适用char[0],再使用length存储串长
    在这里插入图片描述
    特点:直观表示串长,并且数组下标与字符位序相同。

3. 块链存储表示

在这里插入图片描述
存储结构:

  • 链式存储
  • 节点可以存放多个字符增大存储密度
  • 最后一个结点不满时用#补上
typedef struct StringNode{
	char ch[4];
	struct StringNode * next;
}StringNode,*String;

1.3 串的基本操作

串的基本操作

  • StrAssign(&T,chars):赋值操作。把串T赋值为chars。
  • StrCopy(&T,S):复制操作,由串S复制得到串T。
  • StrEmpty(S):判窄操作。若s为空串,则返回TRUE,否则返回FALSE。
  • StrLength(S):求串长。返回串s的元索个数·
  • Clearstring(&S):清空操作。将s清为空串,
  • DestroyString(&S):销毁串。将串s销毁(回收存储空间)。
  • Concat(&T,S1,S2):串联接。用T返回由SI和S2联接而成的新串。
  • SubString(&Sub,S,pos,len):求子串。用sub返回串s的第pos个字符起长度为len的子串。
  • lndex(S,T):定位操作。若主串s中存在与串T值相同的子串,则返回它在主串s中第一次出现的 位置:否则函数值为0·
  • StrCompare(S,T):比较操作。若S>T,则返回值>0:若S=T,则返回值=0:若S<T,则返回值<0。
1. SubString(&Sub,S,pos,len)
bool SubString (SString &Sub,SString S,int pos,int len){
	if (pos+len-1>S.length)
		return false;
	for (int i=pos;i<pos+len;i++){
		Sub.ch[i-pos+1]=S.ch[i];
	}
	Sub.length=len;
	return true;
}
2. StrCompare(S,T)
int StrCompare(SString S,SString T){
	for (int i=1;i<=S.length && i<=T.length;i++){
		if (S.ch[i]!=T.ch[i])
			return S.ch[i]-T.ch[i];
	}
	return S.length-T.length;
}
3. lndex(S,T)
int Index(SString S,SString T){
	int i=1,n=S.length,m=T.length;
	SString sub;
	while (i<=n-m+1){
		SubString(sub,S,i,m);
		if (StrCompare(sub,T)!=0)
			i++;
		else return i;
	}
	return 0;
}

1.2 串的模式匹配

1. 朴素模式匹配算法
  • 串的匹配模式:在主串中找到与模式串相同的子串,并返回其位置。

算法思想与步骤:

  1. 使用i、j分别指示主串S和模式串T 待比较字符的位置。
  2. 从主串的S的第一个字符起,与模式串T的第一个字符比较,若相等则逐个比较后续字符,否则从主串的下个字符起重新与模式串字符比较。
  3. 直至模式串T中的每个字符依次和主串S中的每一个连续的字符序列相等,则匹配成功,返回值为与模式串T第一个字符相等的字符在主串的序号,否则匹配不成功,返回0。

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

int StringIndex(SString S,SString T){
	int k=1;
	int i=k,j=1;
	while (i<=S.length&&j<=T.length){
		if(S.ch[i]==T.ch[j]){
			++i;
			++j;
		}else{
			k++;
			i=k;
			j=1;
		}
	}
	if (j>T.length)
		return k;
	else
		return 0;
}

性能分析

较好的情况:每个子串的第一个字符就与模式串不匹配。

若模式串的长度为m,主串长为n,则:

  • 匹配成功的最好时间复杂度:O(m)
    在这里插入图片描述

  • 匹配失败的最好时间复杂度:O(n-m+1)=O(n-m)=O(n)
    在这里插入图片描述

  • 匹配成功的最坏时间复杂度:O[(n-m+1)*m]=O(mn)
    在这里插入图片描述

  • 匹配失败的最坏时间复杂度:O[(n-m+1)*m]=O(mn)
    在这里插入图片描述

2. 模式匹配算法-KMP

朴素算法缺点:匹配失败后模式串后移一位从头开始比较。某趟已匹配的相等的字符序列是模式的某个前缀,频繁的重复比较相当于模式串进行自我比较。

算法思想与步骤:

  1. 使用i、j分别指示主串S和模式串T 待比较字符的位置。
  2. 从主串的S的第一个字符起,与模式串T的第一个字符比较,若相等则逐个比较后续字符,否则将跳转到子串的next[i]位置重新与主串的当前位置进行比较。
  3. 直至模式串T中的每个字符依次和主串S中的每一个连续的字符序列相等,则匹配成功,返回值为与模式串T第一个字符相等的字符在主串的序号,否则匹配不成功,返回0。
2.1 由PM到next数组
  • 前缀:除了最后一个字符以外,字符串的所有子串。
  • 后缀:除了第一个字符以外,字符串的所有尾部子串。
  • 部分匹配值(Partial Match):前缀和后缀的最长相等前后缀长度值。

可知abcac的部分匹配值(PM)为00010,写为数组形式:

编号12345
Sabcac
PM00010

所以可知,当在第j位匹配失败时,子串的移动位数move:
move=已匹配字符数-对应部分的匹配值=j-1-PM[j-1]

利用数学推导从PM数组到next数组:

  • move=j-1-PM[j-1]
  • next[i]=PM[i]-1 (PM数组整体右移,使得能够使用到不匹配的字符,而不是它的前一个字符)
  • move=(j-1)-next[j]
  • j=j-move=j-((j-1)-next[j])=next[j]+1
编号12345
Sabcac
next01112

next[j]的含义,在子串的第j个字符与主串发生失配时,则跳到子串的next[j]的位置重新与主串的当前位置进行比较

在这里插入图片描述

2.2 next数组的计算

求法:当第j个字符匹配失败时,由1-j-1个字符组成的串记为S,则next[j]=S的最长相等前后缀长度+1。特别地next[1]=0。

函数表达式为:

n e x t [ j ] = { 0 j=1 M a x k ∣ 1 < k < j 且 ′ p 1 . . . p k − 1 ′ = ′ p j − k + 1 . . . p j − 1 ′ 集合不为空时 1 其他情况 next[j]= \begin{cases} 0& \text{j=1}\\ Max {k|1<k<j且'p_1...p_{k-1}'='p_{j-k+1}...p_{j-1}'}& \text{集合不为空时}\\ 1& \text{其他情况} \end{cases} next[j]= 0Maxk∣1<k<jp1...pk1=pjk+1...pj11j=1集合不为空时其他情况

例:
模式串:‘ababaa’

编号123456
Sababaa
next011234

当j=1,next[1]=0
当j=2,‘a’->next[2]=0+1=1
当j=3,‘ab’->next[3]=0+1=1
当j=4,‘aba’->next[4]=1+1=2
当j=5,‘abab’->next[5]=2+1=3
当j=6,‘ababa’->next[6]=3+1=4

void get_next(SString T,int next[]){
	int i=1,j=0;
	next[1]=0;
	while (i<T.length){
		if (j==0 ||T.ch[i]==T.ch[j]){
			++i;
			++j;
			next[i]=j;
		}else {
			j=next[j];
		}
	}
}

KMP算法的平均时间复杂度:O(n+m)

int Index_KMP(SString S,SString T){
	int i=1,j=1;
	int next[MAXLEN];
	get_next(T,next);
	while (i<=S.length && j<=T.length){
		if (j==0 || S.ch[i]==T.ch[j]){
			++i;
			++j;
		}else {
			j=next[j];
		}
		if (j>T.length)
			return i-T.length;
		else 
			return 0;
	}
}
3. KMP算法优化—nextval数组

KMP缺陷:当不匹配字符前方有大量的相同字符时,会增加无意义匹配。
解决方法:优化next数组为nextval数组。

nextval数组求解方法:

  1. 得到模式串的next数组
  2. 从编号为2的字符(假设为q)开始,如果对应的next的编号的字符(假设为p)与该字符(q)相同,将该字符(q)的nextval置为p的next值,如果不同则nextval[j]=next[j]。特别地,nextval[1]=0。
  3. 依次比较,直到模式串结束。

例:
模式串:‘ababaa’

编号123456
Sababaa
next011234
编号123456
Sababaa
nextval010104
void get_nextval(SString T,int next[],int nextval[]){
	nextval[1]=0;
	for (int j=2;j<=T.length;j++){
		if (T.ch[next[j]]==T.ch[j])
			nextval[j]=nextval[next[j]];
		else 
			nextval[j]=next[j];
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值