串的定义
- 串是由零个或多个字符组成的有限序列
- 串中的字符个数n为串的长度
- 字符在串中的位置一般情况下是从1开始数(位序)
串的存储结构
-
顺序存储
#define MAXLEN 255 typedef struct{ char ch[MAXLEN]; int length; }SString
这种方式直接分配一个固定长度的存储区,销毁时系统会自动回收
-
堆分配存储(动态分配)
仍然是一组地址连续的存储单元存放串值,但是存储空间是动态分配得到的
typedef struct{ char *ch;//按串长分配存储区,ch指向串的基地址 int length; }HString //动态分配 S.ch=(char *)malloc(MAXLEN*sizeof(char));
在堆中的存储,用
malloc()
申请的空间,需要手动free()
释放 -
块链存储
用链表方式存储字符串值
一个结点存放一个字符时存储密度低,可以存放多个字符
操作
StrAssign(&T,chars)
:赋值操作,把串T
赋值为chars
StrCompare(S,T)
:S>T
,则返回值>0
;S=T
,则返回值=0
;若S<T
,则返回值<0
StrLength(S)
:求串长SubString(&Sub,S,pos,len)
:求子串,用Sub
返回串S
的第pos
个字符起长度为len
的子串Index(S,T)
:定位操作,返回子串T
在主串S
中首次出现的位置ClearString(&S)
:S
串清空DestroyString(&S)
:销毁,归还空间
简单模式匹配(朴素模式匹配)
子串的定位操作通常叫做模式匹配
算法思想:将主串中与模式串长度相同的子串搞出来,挨个与模式串对比,当子串与模式串某个对应字符不匹配时,就立即放弃当前子串,转而检索下一个子串
最坏时间复杂度:
O(mn)
//朴素模式匹配算法
int Index(SString S,SString T){
int k;//标记主串每次开始比较的位置
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;
}
}
主串多次回溯导致算法效率低
KMP算法
解决了主串回溯的问题
- 字符串前缀:除最后一个字符之外,所有头部子串
- 字符串后缀:除第一个字符之外,所有尾部子串
- 部分匹配值则为字符串的前缀和后缀的最长相等前后缀长度
ababa
:前缀{a
,ab
,aba
,abab
},后缀{a
,ba
,aba
,baba
},最长相等前后缀长度为 3
next数组求法(串的位序从1开始)
当第
j
个字符匹配失败,由前1
至j-1
个字符组成的串记为S
,则next[j]
等于S
的最长相等前后缀长度+1
- next[1]=0;模式串的第一个字符失配时
- next[2]=1;
当串的位序从0开始,
next[0]=-1
next[j]数组含义
在子串的第
j
个字符与主串发生失配时,则跳到子串的next[j]
位置重新与主串当前位置进行比较
nextval[j]
数组如果出现了
Pj=Pnext[j]
时,连续失配,导致比较没有意义解决办法:
next[j]
修正为next[next[j]]
,递归
例
ababaaababaa
编号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
S | a | b | a | b | a | a | a | b | a | b | a | a |
next | 0 | 1 | 1 | 2 | 3 | 4 | 2 | 2 | 3 | 4 | 5 | 6 |
nextval | 0 | 1 | 0 | 1 | 0 | 4 | 2 | 1 | 0 | 1 | 0 | 4 |