一、定义与基本操作
定义:串,即字符串(String),是由零个或者多个字符组成的有限序列,一般记为S=‘a1a2…an’(n$\geq$0),n=0时称为空串
字符集:英文字符——ASCII字符集;中英文——Unicode字符集(编码方案UTF-8、UTF-16等)
注:采用不同的编码方案,每个字符所占空间不同,考研中默认每个字符占用1B即可
串与线性表的对比:
- 串是一种特殊的线性表,数据结构之间呈线性关系
- 串的数据对象限定为字符集(如中文字符、英文字符、数字字符、标点字符等)
- 串的基本操作:比如增删改查通常以字串为操作对象
串的基本操作:假设有串T="",S=“Haaaaax”,W=“HaHahahha”
- StrAssign(&T,chars);——赋值操作:把串T赋值为chars
- StrCopy(&T,S);——复制操作:由串S复制得到串T
- StrEmpty(S);——判空操作:若S为空串,则返回true,否则返回false
- StrLength(S);——判空操作:若S为空串,则返回true,否则返回False
- ClearSting(&S);——清空操作:将S清为空串
- DestoryString(&S);——销毁串:将串S销毁(回收存储空间)
- Concat(&T,S1,S2);——串连接:用T 返回由S1和S2连接而成的新串
- SubString(&Sub,S,pos,len);——求子串:用Sub返回串S的第pos个字符起长度为len的字串(从1开始数)
- Index(S,T);——定位操作:若子串S中存在与串T值相同的子串,则返回它在主串S中第一次出现的位置,否则函数值返回为0
- StrCompare(S,T);——比较操作:若S>T,则返回值>0;若S=T,则返回值=0;若S<T,则返回值<0(ASCII码)
二、存储结构
分为顺序存储和块链存储(链式存储)
顺序存储
- 静态数组(定长的顺序存储)
- 动态数组(堆分配存储)
// 串的顺序存储
#define MAXLEN 255
typedef struct{
char ch[MAXLEN]; // 每个分量存储一个字符
int length; // 串的实际长度
}SString;
// 串的动态数组实现(堆分配存储)
#define MAXLEN 255
typedef struct{
char *ch;
int length;
}HString;
// 使用方式
int main(){
HString S;
S.ch=(char *)malloc(MAXLEN*sizeof(char));
S.length=0;
}
串的部分基本操作算法实现:
// 基本操作的实现
// 求子串
bool SubString(SString &Sub,SString S,int pos,int len){// 用SUb返回串S的第pos个字符串起长度为len的子串
if(pos+len>S.length){ //长度或者pos越界
return false;
}
for(int i=pos;i<pos+len;i++){
Sub.ch[i-pos+1] = S.ch[i];
}
Sub.length=len;
return false;
}
// 比较
int StrCompare(SString S,SString T){ // 比较操作,如果S>T,则返回数值》0,S=T,返回数值=0,S<T返回数值<0
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;
}
// 定位
int Index(SString S,SString T){
int i=1,n=StrLength(S),m=StrLength(T); //StrLength(T)——求串长
SString sub; // 用于暂存子串
while(i<=n-m+1){
SubString(sub,S,i,m);
if(StrCompare(sub,T)!=0)
++i;
}
return 0;
}
串的链式存储
存储方式(使用下面的方式)每个结点存放多个字符,没有字符的位置用’#‘或者’\0’补足
// 串的链式存储
// 块链存储方式1:
typedef struct StringNode{
char ch; //每个结点存一个字符
struct StringNode *next;
} StringNode,*String;
// 块链存储方式2:提高存储密度,最后一个块不足的用'#'或者'\0'表示
typedef struct StringNode{
char ch[4]; //每个结点存4个字符
struct StringNode *next;
} StringNode,*String;
三、串的应用——字符串模式匹配算法
被搜索的串为主串,需要搜索的串为模式串(与主串不同,模式串不一定能在主串中找到)
字符串模式匹配:在主串中找到模式相同的字串,并返回其所在位置
朴素模式匹配算法
下边的算法:设主串⻓度为 n,模式串⻓度为 m,则:
- 最坏时间复杂度 : O(nm);
- 最好时间复杂度:最好的情况,每个⼦串的第⼀个字符就匹配失败,共 n-m+1 个⼦串,复杂度 = O(n-m+1) = O(n)
朴素模式匹配算法:将主串中所有⻓度为m的⼦串(最多有n-m+1个)依次与模式串对⽐,直到找到⼀个完全匹配的⼦串,或所有的⼦串都不匹配为⽌。
- 比如上边的定位操作Index(S,T)的代码实现逻辑
- 常用方式:直接使用数组下标 当前字串匹配失败i=i-j+2; j=1;匹配成功:i-T.length,暴力解决问题,如下:
// 字符串的模式匹配
// 朴素模式匹配算法
int Index(SString S,SString T){
int i=1,j=1; // i记录被对比的字符串的位序,j记录需要对比的字符串的位序
while(i<=S.length && j<=T.length){
if(S.ch[i]==T.ch[i]){
++i;
++j;
}else{
i=j-i+2; // 对比失败,回到这一次对比的初始元素的下一个位置
j=1; // j回到T的第一个位置,再次开始对比
}
}
if(j>T.length){ //字符串对比成功?
return i-T.length; //返回T在S中开始的位置
}else
return 0;
}
KPM算法
//KMP算法
// 核心思想——主串指针不回溯
// 主要操作,求出next[]数组
// next[]数组的求法需要用到字符串的前缀、后缀和部分匹配值——需要事先求出
int Index_KMP(SString S,SString T,int next[]){
int i=1,j=1; // i记录被对比的字符串的位序,j记录需要对比的字符串的位序
while(i<=S.length && j<=T.length){
if(j==0||S.ch[i]==T.ch[i]){
++i;
++j;
}else{
j=next[j]; // next数组中的next[j]记录的是匹配到第j个位置时候匹配失败,则j需要移动到的位置
}
}
if(j>T.length){ //字符串对比成功?
return i-T.length; //返回T在S中开始的位置
}else
return 0;
}
next数组求解
next数组的作用:当模式串的第j个字符失配时,从模式串的第nextj]的继续往后匹配
任何模式串都一样,第一个字符不匹配时,只能匹配下一个子串,因此,往后余生,next[1]==0(如果j=0,下一步i++;j++;)
任何模式串都一样,第二个字符不匹配时,应尝试匹配模式串的第一个字符,因此,往后余生,next[2]==1
在不匹配的位置前边,画一条美丽的分界线,模式串一步一步往后退,直到分界线之前“能对上”,或者模式串完全跨过分界线为止
此时j指向哪儿,next数组就是多少