串是有限长的字符序列,由一对单引号相括,如: “a string”
可以理解为c++的
s
t
r
i
n
g
string
string
基本操作
S
t
r
A
s
s
i
g
n
,
S
t
r
C
o
m
p
a
r
e
,
S
t
r
L
e
n
g
t
h
,
C
o
n
c
a
t
,
S
u
b
S
t
r
i
n
g
StrAssign,StrCompare,StrLength,Concat,SubString
StrAssign,StrCompare,StrLength,Concat,SubString是基本操作集
定长顺序存储表示
#define MAXSTRLEN 255 // 用户可在255以内定义最大串长
typedef unsigned char Sstring[MAXSTRLEN + 1]; // 0号单元存放串的长度
就是用了char数组模拟string,只不过在数组首位放了数组长度
串的实际长度可在这个予定义长度的范围内随意设定,超过予定义长度的串值则被舍去,称之为“截断”
基本操作
Status SubString(SString &Sub, SString S, int pos, int len) {
// 用Sub返回串S的第pos个字符起长度为len的子串。
//其中,1≤pos≤StrLength(S)且0≤len≤StrLength(S)-//pos+1 。
if ( pos<1 || pos>S[0] || len<0 || len>S[0]-pos+1 )
return ERROR;
Sub[1..len] = S[pos..pos+len-1];
Sub[0] = len; return OK;
}//SubString
注意S[0]-pos+1这里需要+1
堆分配存储表示
typedef struct {
char *ch; // 若是非空串,则按串长分配存储区,否则ch为NULL
int length; // 串长度
} HString;
通常,C语言中提供的串类型就是以这种存储方式实现的。系统利用函数malloc( )和free( )进行串值空间的动态管理,为每一个新产生的串分配一个存储区,称串值共享的存储空间为“堆”。C语言中的串以一个空字符为结束符, 串长是一个隐含值。简而言之:通过malloc(),free()为串动态分配存储空间
基本操作
Status StrInsert(HString &S, int pos, HString T) {
// 1≤pos≤StrLength(S)+1。
//在串S的第pos个字符之前插入T
if (pos<1 || pos>S.length+1 ) return ERROR;
// pos不合法
if (T.length) {
//T非空,则重新分配空间,插入T
if (!(S.ch = (char *) realloc ( S.ch, ( S.length+T.length ) * sizeof ( char ))))
exit (OVERFLOW);
for ( i=S.length-1;i>=pos-1; --i)
//为插入T而腾出位置
S.ch[i+T.length] = S.ch[i];
S.ch[pos-1..pos+T.length-2] = T.ch[0..T.length-1]; //插入T
S.Length += T.length;
}
return OK;
} // StrInsert
注意这一步中的S.ch[pos-1..pos+T.length-2]
,感觉这些细节其实不那么能注意到
这里i=S.length-1
,是因为:pos给出的视角是数组从1开始到
l
e
n
g
t
h
length
length的存储
[
1
]
…
…
[
l
e
n
g
t
h
]
[1]……[length]
[1]……[length],而实际上堆分配的串的存储应该是从0开始的
[
0
]
…
…
[
l
e
n
g
t
h
−
1
]
[0]……[length-1]
[0]……[length−1]
r e a l l o c ( ) realloc() realloc()
- 如果 size 的值为 0,则等价于调用 free(ptr),即释放 ptr 所指向的内存空间。
- 如果传入的 ptr 不为 NULL,并且 size 大于原先分配的内存大小,则尝试将之前分配的内存空间扩展为 size 字节的大小。如果当前内存不足以扩展到 size 字节,则重新分配一块新的内存空间,并将原有空间中的数据复制到新的内存空间上,最后释放原有的内存空间,返回新的内存空间的地址。
- 如果传入的 ptr 不为 NULL,并且 size 小于或等于原先分配的内存大小,则尝试缩小原有的内存空间到指定的大小。如果原有的内存空间过大,多余的部分会被返回给内存管理系统。
Status SubString(HString& Sub, HString S, int pos, int len) {
// 用 Sub 返回串 S 的第 pos 个字符起长度为 len 的子串
if (pos < 1 || pos > S.length || len < 0 || len > S.length - pos + 1) return ERROR;
if (Sub.ch) free(Sub.ch); // 释放旧空间
if (!len) {
Sub.ch = NULL;
Sub.length = 0; // 空子串
} else {
Sub.ch = (char*)malloc(len * sizeof(char)); // 完整子串
for (int i = 0; i < len; i++) Sub.ch[i] = S.ch[pos + i - 1];
Sub.length = len;
}
return OK;
} // SubString
仍然要强调的是,注意特殊情况进行特殊判断
串的块链存储表示
链表来存储串值,由于串的数据元素是一个字符,因此用链表存储时,通常一个结点中可以存放一个字符,也可以存放多个字符。
#define CHUNKSIZE 80 // 可由用户定义的块大小
typedef struct Chunk {
char ch[CHUNKSIZE];
struct Chunk* next;
} Chunk;
typedef struct {
Chunk *head, *tail; // 串的头和尾指针
int curlen; // 串的当前长度
} LString;
实际应用时,可以根据问题所需来设置结点的大小。
例如: 在编辑系统中,整个文本编辑区可以看成是一个串,每一行是一个子串,构成一个结点。即: 同一行的串用定长结构(80个字符), 行和行之间用指针相联接。
串的模式匹配算法
朴素模式匹配
int Index(SString S, SString T, int pos) {
// 返回子串 T 在主串 S 中第 pos 个字符之后的位置。若不存在,
// 则函数值为 0。其中,T 非空,1 <= pos <= StrLength(S)。
i = pos;
j = 1;
while (i <= S[0] && j <= T[0]) { // 0 下标存储字符串长度
if (S[i] == T[j]) {
++i;
++j; // 继续比较后继字符
} else {
i = i - j + 2;
j = 1; // 指针后退重新开始匹配
}
}
if (j > T[0]) return i - T[0];
else return 0;
} // Index
关于 i = i − j + 2 i = i - j + 2 i=i−j+2 的含义:当在匹配中出现了不匹配的情况时,我们需要将指针 i i i 和 j j j 同时后移一位再进行匹配,以尝试匹配剩下的部分。由于当前指针 i 已经匹配过了前 j − 1 j - 1 j−1个字符,因此需要将 i 前移 j − 1 - 1 −1 位,再加上 2 2 2,即可保证 i i i恰好后移一位,准备与 j j j 再次匹配。
这种位置类的细节,完全值得好好注意虽然暂时还不知道考试会不会扣这方面的细节,但是自己写代码的时候,也需要避免越界访问
时间复杂度为:O(m*n)
KMP算法
int Index_KMP(SString S, SString T, int pos) {
// 利用模式串 T 的 next 函数求 T 在主串 S 中第 pos 个字符之后的位置的 KMP 算法。
// 其中,T 非空,1 ≤ pos ≤ StrLength(S)
int next[MAXSIZE];
GetNext(T, next);
int i = pos;
int j = 1;
while (i <= S[0] && j <= T[0]) { // 0 下标存储字符串长度
if (j == 0 || S[i] == T[j]) {
++i;
++j; // 继续比较后继字符
} else {
j = next[j]; // 模式串向右移动
}
}
if (j > T[0]) {
return i - T[0] + 1; // 匹配成功
} else {
return 0;
}
} // Index_KMP
这个算法稍微有点复杂
讲解
其中,next数组要自己弄出来