4.1串的定义和基本操作
串,即字符串,是由零个或多个字符 组成的有限 序列,一般记为:S=‘a1a2…an’;
其中S是串名,单引号内是串的值;
ai可以是字母、数字或其他字符;
串中的字符个数n称为串的长度 ,n=0时的串称为空串。
子串:串中任意个连续的字符组成的子序列。
主串:包含子串的串。
字符在主串中的位置:字符在串中的序号。
子串在主串中的位置:子串的第一个字符在串中的序号。
空串VS空格串:M=’’ VS N=’ ’ 空格占字节
串是一种特殊的线性表,数据元素之间成线性关系
串的数据对象限定为字符集(如中文字符、英文字符、数字字符、标点字符等)
串的基本操作,如增删改查等通常以字串为操作对象
基本操作:
假设有串T = “”,S = “iPhone 11 Pro Max?”,W = “Pro”
- StrAssign(&T,chars):赋值操作,把串T赋值为chars
- StrCopy(&T,S):复制操作,由串复制得到串T
- StrEmpty(S):判空操作,若S为空串则返回true,否则返回false
- StrLength(S):求串长,返回串S的元素个数
- ClearString(&S):清空操作,将S清为空串
- DestroyString(&S):销毁串,回收S所占存储空间
- Concat(&T,S1,S2):串连接,用T返回用S1和S2连接而成的新串
- SubString(&Sub,S,pos,len):求子串,用Sub返回串S的第pos个字符起长度为len的子串
- Index(S,T):定位操作,若主串S中存在与串T值相同的子串,则返回它在主串S中第一次出现的位置,否则函数值为0
- StrCompare(S,T):比较操作,若S>T则返回值>0,若S=T则返回值=0,若S<T则返回值<0
字符集编码拓展:
任何数据存到计算机中一定是二进制数
需要确定一个字符和二进制数的对应规则就是“编码”
字符集:英文字符----ASCII字符集,中英文----Unicode字符集;基于同一字符集可以有多种编码方案,如:UTF-8、UTF-16;采用不同编码方式每个字符所占空间不同,默认每个字符占1B。
y=f(x)
字符集:函数定义域
编码:函数映射规则f
y:对应二进制数
4.2串的存储结构和基本操作的实现
4.2.1代码展示
//静态数组实现顺序存储
#define MAXLEN 255 //预定义最大串长
typedef struct{
char ch[MAXLEN];//每个分量存储一个字符
int length;//串的实际长度
}SString;
//动态数组实现堆分配存储
typedef struct{
char *ch;//按串长分配存储区,ch指向串的基地址
int length;//串的长度
}HString;
HString S;
S.ch=(char *)malloc(MAXLEN * sizeof(char));
S.length=0;
//用完收到free
//串的链式存储
typedef struct StringNode{
char ch;//每个结点存一个字符 1B
struct StringNode *nest;//4B
}StringNode,* String;//存储密度低
typedef struct StringNode{
char ch[4];//每个结点存多个字符
struct StringNode *nest;//4B
}StringNode,* String;//存储密度提高
4.2.2串的顺序存储方案
4.2.3基本操作的实现
以顺序存储为例(方案四)
//求字串,用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;
}
//比较操作,若S>T则返回值>0,若S=T则返回值=0,若S<T则返回值<0
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;
}
//定位操作,若主串S中存在与串T值相同的子串,则返回它在主串S中第一次出现的位置,否则函数值为0
int Index(SString S,SString T){
int i=1, n=StrLength(S), m=StrLength(T);
SString sub;//用于暂存子串
while(i<=n-m+1){
SubString(sub,S,i,m);
if(StrCompare(sub,T)!=0)
i++;
else
return i;//返回子串在主串中的位置
}
return 0;//S中不存在与T相等的子串
}
4.3串的模式匹配
在主串中找到与模式串相同的子串,并返回其所在位置
4.3.1朴素模式匹配算法
将主串中与模式串长度相同的子串搞出来,挨个与模式串对比
当子串与模式串某个对应字符不匹配时,就立即放弃当前子串,转而检索下一个子串
当检索成功时返回第一个字符位置,不成功则返回0
若模式串长度为m,主串长度为n,则直到匹配成功/匹配失败最多需要(n-m+1)*m次比较,时间复杂度:O(mn)
成功最好时间复杂度:O(m),第一个子串就成功,即只比较子串长度次数
失败最好时间复杂度:O(n),子串第一个字符与主串完全不匹配,只比较主串长度次数
//法1:借助变量k记录位置
int Index(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)//匹配成功终止条件,跳出while循环是因为j越界了
return k;
else
return 0;
}
//法2:不借助k
int Index(SString S,SString T){
int i=1,j=1;
while(i<=S.length && j<=T.length){//有效范围
if(S.ch[i]==T.ch[j]){//字符匹配成功
++i;
++j;
}else{
i=i-j+2;//i=i-(j-1)+1=i-j+2,主串回溯
j=1;//初始化
}
}
if(j>T.length)//匹配成功终止条件,跳出while循环是因为j越界了
return i-T.length;
else
return 0;
}
4.3.2改进模式匹配算法——KMP算法
//最重要的就是求出next数组
int Index(SString S,SString T,int next[]){
int i=1,j=1;
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)//匹配成功终止条件,跳出while循环是因为j越界了
return i-T.length;
else
return 0;
}
手算求next数组:
串的前缀:包含第一个字符,且不包含最后一个字符的子串
串的后缀:包含最后一个字符,且不包含第一个字符的子串
当第 j 个字符匹配失败,由前1 ~ j-1 个字符组成的串记为S,则:
next[j] = S的最长相等前后缀长度+1
特别的,next[1]=0,next[2]=1
例:ababaa; 注:粗体前缀,斜体后缀
j=1:S=空;next[1]=0
j=2:S=a,0+1=1;next[2]=1
j=3:S=ab,0+1=1;next[3]=1
j=4:S=aba,1+1=2;next[4]=2
j=5:S=abab,2+1=3;next[5]=3
j=6:S=aba ba,3+1=4;next[6]=4
序号 j | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
模式串 | a | b | a | b | a | a |
next[j] | 1 | 1 | 2 | 3 | 4 |
//求模式串T的next数组
void get_next(SString T,int next[]){
int i=1,j=0;
next[1]=0;
while(i<T.length){
++i;
++j;
next[i]=j;
}else
j=next[j];
}
//KMP
int Index(SString S,SString T){
int i=1,j=1;
int next[T.length+1];
get_next(T,next);//求模式串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)//匹配成功终止条件,跳出while循环是因为j越界了
return i-T.length;
else
return 0;
}
时间复杂度:O(n+m)
4.3.3 KMP算法进一步优化
优化next数组为nextval数组,减少不必要比较
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];
}
}//仔细看看有好处
序号 j | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
模式串 | a | b | a | b | a | a |
next[j] | 1 | 1 | 2 | 3 | 4 | |
nextval[j] | 1 | 1 | 4 |