串的定义
字符串String:是由零个或多个字符组成的有限序列。
一般记为S="a₁a₂a₃a₄a₅....":S是串名,引号括起来的字符序列是串的值,字符的个数为串的长度
- 子串:串中任意个连续的字符组成的子序列
- 主串:包含子串的串
- 字符在主串中的位置:字符在串中的序号
- 子串在主串中的位置:子串的第一个字符在主串中的位置
- 串是一种特殊的线性表,数据元素之间呈线性关系
- 串的数据对象限定为字符集
- 串的基本操作,如增删改查等通常以子串为操作对象
串的基本操作
//假设有串T = "",S = "iPhone 11 Pro Max?",W = "Pro"
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返回由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.
//【字符集编码】:每个字符在计算机中对应一个二进制数,比较字符的大小其实就是比较二进制数的大小
串的存储结构
顺序存储
结构体
#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;
*/
/*
方案一:专门变量记录length
方案二:ch[0]充当Length,字符的位序和数组下标相同
方案三:没有Length变量,以字符'\0'表示结尾(对应ASCII码的0)
方案四:舍弃ch[0],设置Length变量
*/
求子串
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;
}
比较
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;
}
定位
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相等的子串
}
链式存储
结构体
typedef struct StringNode{
char ch; //存储密度低
struct StringNode *next;
}StringNode,*String;
typedef struct StringNode{
char ch[5];
struct StringNode *next;
}StringNode,*String;
字符串的模式匹配
在主串中找到与模式串相同的子串,并返回其所在位置
朴素模式匹配算法
将主串中所有长度位m的子串依次与模式串对比,直到找到一个完全匹配的子串,或所有的子串都不匹配为止
//Index(S,T):通过基本操作实现
//不使用字符串的基本操作,直接通过数组下标实现朴素模式匹配算法
//若当前子串匹配失败,则主串指针i指向下一个子串的第一个位置,模式串指针j回到模式串的第一个位置
//若j>T.length,则当前子串匹配成功,返回当前子串的第一个字符的位置 i-T.length
int Index(SString S,SString T){
int i = 1;
int j = 1;
while(i <= S.length && j <= T.length){
if(S.ch[i] == T.ch[i]){
++i;
++j; //继续比较后续字符
}
else{
i = i - j + 2;
j = 1; //指针后退重新开始匹配
}
}
if(j > T.length)
return i - T.length;
else
return 0;
}
/*
最坏时间复杂度 = O(nm)
每个子串都要对比m个字符,共n-m+1个子串,复杂度 = O((n-m+1)m) = O(nm)
*/
KMP算法
每当一趟匹配过程中出现字符比较不等时,不需回溯i指针,而是利用已经得到的"部分匹配"的结果将模式向右"滑动"尽可能远的一段距离后,继续进行比较
//不匹配的字符之前,一定是和模式串一致的
int Index_KMP(SString S,SString T,int next[]){
int i = 1;
int j = 1;
while(i <= S.length && j <= T.length){
if(j == 0 || S.ch[i] == T.ch[j]){
++i;
++j; //继续比较后继字符
}
//匹配失败时,主串指针i不回溯
else
j = next[j]; //模式串向右移动
}
if(j > T.length)
return i-T.length;//匹配成功
else
return 0;
}
/*
最坏时间复杂度:O(m+n)
求next数组时间复杂度 O(m)
模式匹配过程最坏时间复杂度O(n)
*/
void getNext(char t[],int next[]){
next[0] = 0;
for(int i = 1,j = 0;i < strlen(t);i++){
while(j > 0 && t[i] != t[j]){
j = next[j-1];
}
if(t[i] == t[j])
j++;
next[j] = j;
}
}
//next[1] = 0;next[2] = 1;
//对next数组进行进一步的优化-->nextval数组
void getNextval(SString T,int next[],int nextval[]){
nextval[1] = 0;
for(int j = 2; j <= T.length;j++){
//判断原j对应的next[j]指向的串元素,是否与j对应的串元素相等,如相等则直接替换,不等则下一个
if(T.ch[next[j]] == T.ch[j])
nextval[j] = nextval[next[j]];
else
nextval[j] = next[j];
}
}