串和KMP算法
1、串的抽象数据类型的定义
ADT String {
数据对象:
D = { a i ∣ a i ∈ C h a r a c t e r S e t ; i = 1 , 2 , . . . , n ; n ⩾ 0 } D=\{a_{i} | a_{i} \in CharacterSet; \ i=1,2,...,n;\ n \geqslant 0\} D={ai∣ai∈CharacterSet; i=1,2,...,n; n⩾0}
数据关系:
R 1 = { < a i − 1 , a i > ∣ a i − 1 , a i ∈ D ; i = 1 , 2 , . . . , n } \begin{aligned}R_{1}=\{&<a_{i-1},a_{i}> | a_{i-1},a_{i} \in D;\ i=1,2,...,n\}\end{aligned} R1={<ai−1,ai>∣ai−1,ai∈D; i=1,2,...,n}
基本操作:
StrAssign(&T, chars)
初始条件:chars是字符串常量。
操作结果:生成一个其值等于chars的串T。
StrCoрy(&т, S)
初始条件:串S存在。
操作结果:由串S复制得串T。
StrEmpty(S)
初始条件:串S存在。
操作结果:若S为空串,则返回TRUE,否则返回FALSE。
StrCompare(S, T)
初始条件:串S和T存在。
操作结果:若S > T,则返回值 >0;若S = T,则返回值 =0;若S < T,则返回值 <0。
StrLength(S)
初始条件:串S存在。
操作结果:返回S的元素个数,称为串的长度。
ClearString(&S)
初始条件:串S存在。
操作结果:将S清为空串。
Concat(&T, S1, S2)
初始条件:串S1和S2存在。
操作结果:用T返回由S1和S2联接而成的新串。
SubString(&Sub, S, pos, len)
初始条件:串S存在, 1 ⩽ p o s ⩽ S t r L e n g t h ( S ) 1 \leqslant pos \leqslant StrLength(S) 1⩽pos⩽StrLength(S) 且 0 ⩽ l e n ⩽ S t r L e n g t h ( S ) − p o s + 1 0 \leqslant len \leqslant StrLength(S)-pos+1 0⩽len⩽StrLength(S)−pos+1。
操作结果:用Sub返回串S的第pos个字符起长度为len的子串。
Index(S, T, pos)
初始条件:串S和T存在,T是非空串, 1 ⩽ p o s ⩽ S t r L e n g t h ( S ) 1 \leqslant pos \leqslant StrLength(S) 1⩽pos⩽StrLength(S)。
操作结果:若主串S中存在和串T值相同的子串,则返回它在主串S中第pos个字符之后第一次出现的位置;否则函数值为0。
Replace(&S, T, V)
初始条件:串S,T和V存在,T是非空串。
操作结果:用V替换主S中出现的所有与T相等的不重叠的子串。
StrInsert(&S, pos, T)
初始条件:串S和T存在, 1 ⩽ p o s ⩽ S t r L e n g t h ( S ) + 1 1 \leqslant pos \leqslant StrLength(S)+1 1⩽pos⩽StrLength(S)+1。
操作结果:在串S的第pos个字符之前插入串T。
StrDelete(&S, pos, len)
初始条件:串S存在, 1 ⩽ p o s ⩽ S t r L e n g t h ( S ) − l e n + 1 1 \leqslant pos \leqslant StrLength(S)-len+1 1⩽pos⩽StrLength(S)−len+1。
操作结果:从串S中删除第pos个字符起长度为len的子串。
DestroyString(&S)
初始条件:串S存在。
操作结果:串S被销毁。
}ADT String
串赋值StrAssign、串比较StrCompare、求串长StrLength、串联接Concat,求子串SubString 5种操作构成串类型的最小操作子集。即其他串操作(除串清除ClearString,串销毁DestroyString外)均可在这个最小操作子集上实现。
2、串的表示和实现
2.1、定长顺序存储表示
类似于线性表的顺序存储结构,用一组地址连续的存储单元存储串值的字符序列。在串的定长顺序存储结构中,按照预定义的大小,为每个定义的串变量分配一个固定长度的存储区,则可用定长数组如下描述之。
// -----串的定长顺序存储表示----
#define MAXSTRLEN 255 // 用户可在255以内定义最大串长
typedef unsigned char SString[MANSTRLEN + 1]; // 0号单元存放串的长度
// MAX_SIZE 表示串的最大长度;
#define MAX_SIZE 10
typedef struct Str {
// str数组长度定义为 MAX_SIZE + 1,因为多出一个'\0'作为结束标记;
char str[MAX_SIZE + 1];
int length; //串长度
} Str;
串的实际长度可在这预定义长度的范围内随意,超过预定义长度的串值则被舍去,称之为“截断”。
对串长有两种表示方法:
一是以下标为0的数组分量存放串的实际长度。
二是在串值后面加一个不计人串长的结束标记字符,此时的串长为隐含值,显然不便于进行某些串操作。
2.2、堆分配存储表示
通常C语言中提供的串类型就是以这种存储方式实现的。系统利用malloc()和free()进行串值空间的动态管理,为每一个新产生的串分配一个存储区,称串值共享的存储空间为“堆”,C语言中的串以一个空字符为结束符,串长是一个隐含值。
// -----串的堆分配存储表示----
typedef struct{
char *ch; //若是非空串,则按串长分配存储区,否则ch为NULL
int length; //串长度
}HString;
typedef struct Str {
char *ch;
int length; // 串长度
} Str;
//初始化
Str S;
S.length = L;
S.ch = (char*) malloc((L + 1) * sizeof(char));
//S.ch[length范围内的位置] = 某字符变量
//某字符变量 = S.ch[length范围内的位置]
//释放内存
free(S.ch)
2.3、串的块链存储表示
和线性表的链式存储结构相类似,也可采用链表方式存储串值。由于串结构的特殊性——结构中的每个数据元素是一个字符,则用链表存储串值时,存在一个 “结点大小” 的问题,即每个结点可以存放一个字符,也可以存放多个字符。
当结点大小大于1时,由于串长不一定是结点大小的整倍数,则链表中的最后一个结点不一定全被串值占满,此时通常补上 “#" 或其他的非串值字符(通常 “#” 不属于串的字符集,是一个特殊的符号)。
// ======串的块链存储表示======
#define CHUNK_SIZE 80 //可由用户定义的块大小
typedef struct Chunk{
char ch[CHUNK_SIZE];
struct Chunk *next;
}Chunk;
typedef struct {
Chunk *head, *tail; //串的头和尾指针
int curlen; //串的当前长度
}LString;
串值的存储密度 = 串值所占的存储位 / 实际分配的存储位
3、字符串模式匹配算法
3.1、简单算法
采用定长顺序存储结构的不依赖于其他串操作的匹配算法:
int Index(SString S, SString T, int pos){
//返回子串T在主串S中第pos个字符之后的位置;若不存在,函数返回值为0。
//其中,T非空,1<=pos<=StrLength(S)
int i = pos, j = 1;
while(i<=S[0] && j<=T[0]){
if(S[i] == T[j]){//继续比较后续字符
++i;
++j;
}else{//指针后退重新开始匹配
i = i-(j-2); // 或 i = i - j + 2;
j = 1;
}
}
if(j>T[0]){
return i-T[0];
}else{
return 0;
}
}
3.2、首尾匹配算法
先比较模式串的第一个字符,
再比较模式串的最后一个字符,
最后比较模式串中从第二个到第n-1个字符。
int Index_FL(SString S, SString T, int pos){
sLength = S[0];
tLength = T[0];
int i = pos;
patStartChar = T[1];
patEndChar = T[tLength];
while(i <= sLength-tLength+1){
if(S[i] != patStartChar){
++i; //重新查找匹配起始点
}else if(S[i+tLength-1] != patEndChar){
++i; //模式串的"尾字符"不匹起
}else{ //检查中间字符的匹配情况
}
}
return 0;
}
3.3、KMP算法
《部分匹配表》(Partial Match Table)
"前缀"指除了最后一个字符以外,一个字符串的剩余全部字符;
"后缀"指除了第一个字符以外,一个字符串的剩余全部字符;
KMP算法的时间复杂度可以达到O(m+n)
经过简单匹配过程可知:
当 S[i] != T[j] \LARGE{\textbf{S[i]\ !=\ T[j]}} S[i] != T[j] 时
已经得到的结果是 S[i-(j-1)...(i-1)] == T[1...(j-1)] \LARGE{\textbf{S[i-(j-1)...(i-1)]\ ==\ T[1...(j-1)]}} S[i-(j-1)...(i-1)] == T[1...(j-1)]
若已知 T[1...(k-1)] == T[j-(k-1)...(j-1)] \LARGE{\textbf{T[1...(k-1)]\ ==\ T[j-(k-1)...(j-1)]}} T[1...(k-1)] == T[j-(k-1)...(j-1)]
则有 S[i-(k-1)...(i-1)] == T[1...(k-1)] \LARGE{\textbf{S[i-(k-1)...(i-1)]\ ==\ T[1...(k-1)]}} S[i-(k-1)...(i-1)] == T[1...(k-1)]
定义模式串的next函数
n
e
x
t
(
j
)
=
{
0
当 j = 1 ;
M
a
x
{
k
∣
1
<
k
<
j
且
′
p
1
p
2
⋅
⋅
⋅
p
k
−
1
′
=
′
p
j
−
(
k
−
1
)
p
j
−
(
k
−
2
)
⋅
⋅
⋅
p
j
−
1
′
}
1
其他情况
\large{ next(j)= \begin{cases} 0& \text{当\ j\ =\ 1\ ;}\\ Max\{k\ |\ 1<k<j\ 且'p_{1}p_{2}\cdot \cdot \cdot p_{k-1}'='p_{j-(k-1)}p_{j-(k-2)}\cdot \cdot \cdot p_{j-1}'\ \}&\\ 1& \text{其他情况} \end{cases} }
next(j)=⎩⎪⎪⎨⎪⎪⎧0Max{k ∣ 1<k<j 且′p1p2⋅⋅⋅pk−1′=′pj−(k−1)pj−(k−2)⋅⋅⋅pj−1′ }1当 j = 1 ;其他情况
3.3.1、KMP
int Index_KMP(SString S, SString T, int pos) {
// 1<=pos<=StrLength(S)
int i=pos, j=1;
while(i<=S[0] && j<=T[0]){
if(j==0 || S[i]==T[j]){//继续比较后续字符
++i;
++j;
}else{ //模式串向右移动
j=next[j];
}
}
if(j > T[0]){ //匹配成功
return i-T[0];
}else{
return 0;
}
} //Index_KMP
3.3.2、next数组
void get_next(SString &T int &next[]){
//求模式串T的next函数值并存入数组next。
int i = 1;
next[1] = 0;
int j = 0;
while(i<T[0]){
if(j==0 || T[i]==T[j]){
++i;
++j;
next[i]=j;
}else{
j=next[j];
}
}
}//get_next
![](https://i-blog.csdnimg.cn/blog_migrate/d2891ea60fc5eac4150127131a322604.png)
对于模式串
a
b
a
b
c
a
b
a
b
a
b
c
\large{ababcabababc}
ababcabababc,求next数组过程
当i=1时,next[1]=0
;
此后
第一次进入while循环,此时 j 初始化为0;进入if语句,{ ++i 即 i=2
; ++j 即 j=1
; next[2]=1
;}
第二次进入while循环,此时 if 判断不符合,进入else语句,{ j = next[1] = 0
; }
第三次进入while循环,此时 j=0,进入 if 语句,{ ++i 即 i=3
; ++j 即 j=1
; next[3]=1
;}
第四次进入while循环,此时 T[3]==T[1],进入 if 语句,{ ++i 即 i=4
; ++j 即 j=2
; next[4]=2
;}
. . . . . . 从而做到在不回溯i, 不断向前推移模式串的方法下求取next数组。
![](https://i-blog.csdnimg.cn/blog_migrate/fcb9cbfed1e90324722c1fc74cea079b.png)
3.3.3、nextval数组
当面临下面这种特殊情况时
S=’aaabaaabaaabaaabaaab’
\LARGE{\textbf{S='aaabaaabaaabaaabaaab'}}
S=’aaabaaabaaabaaabaaab’
T=’aaaab’
\LARGE{\textbf{T='aaaab'}}
T=’aaaab’
next[j] = 01234
nextval[j] = 00004
void get_nextval(SString &T, int &nextval[]) {
int i = 1;
nextval[1] = 0;
int j = 0;
while(i<T[0]){
if(j==0 || T[i]==T[j]){
++i;
++j;
if(T[i] != T[j]){
nextval[i] = j;
}else{
nextval[i] = nextval[j];
}
}else{
j = nextval[j];
}
}
} //get_nextval
=======================================
待整理 串的模式匹配算法
串的基本操作
赋值操作:将一个常量字符串赋值给str;赋值成功返回1,否则返回0
int strassign(Str& str, char* ch){
if(str.ch){
free(str.ch)//释放原串空间
}
int len = 0;
char *c = ch;
while(*c){//求ch串的长度
++len;
++c;
}
if(len ==0){//如果ch为空串,则直接返回空串
str.ch = NULL;
str.length=0;
return 1;
}else{
str.ch = (char*)malloc(sizeof(char) * (len+1));
//取len+1是为了多分配一个空间存放'\0'字符
if(str.ch == NULL){
return 0;
}else{
c = ch;
for(int i=0;i<=len;++i,++c){
str.ch[i]=*c;
}
//注意:循环条件中之所以"<=",是为了将ch最后的'\n'复制到新串中作为结束标记
str.length = len;
return 1;
}
}
}
函数使用格式:
strassign(str,"Hello World")
取串长度的操作
int strlength(Str str){
return str.length;
}
在没有给出串长度信息的情况下,求长度的操作可以借鉴函数strassign()中的求输入串长度的部分代码来实现
串的比较操作
int strcompare(Str s1, Str s2){
for(int i=0;i<s1.length && i<s2.length;++i){
if(s1.ch[i] != s2.ch[i]){
return s1.ch[i] - s2.ch[i]
}
}
return s1.length = s2.length;
}
串连接操作
求子串操作
串清空操作
int clear(){
if(str.ch){
free(str.ch);
str.ch = NULL;
}
str.length-0;
return 1
}
——《数据结构图 (C语言版) 严蔚敏》 学习笔记