第四章 串,数组和广义表
串(String)
零个或多个任意字符组成的有限序列
包含串名,串值,串长n
n=0为空串
- 子串:一个串中任意个连续字符组成的子序列(含空串)称为该串的子串
- 真子串:指不包含自身的所有子串
- 主串:包含子串的串,相应的成为主串
- 字符位置:字符在序列中的序号为该字符在串中的位置
- 子串位置:子串第一个字符在主串中的位置
- 空格串:由一个或多个空格组成的串,与空串不同
- 串相等:当且仅当两个串的长度相等,并且各个对应位置上的字符都相同时,这两个串才相等。所有空串都是相等的
串的存储结构
顺序存储结构,也就是顺序串,匹配运算和查找比较方便
// 串的循序存储结构
typedef struct SString{
char ch[MAXLEN + 1];
int length;
}SString;
链式存储结构,也即是链串
// 串的链式存储结构(块链)
#define MAXCHUNKSIZE 80
typedef struct Chunk {
char ch[MAXCHUNKSIZE];
struct Chunk * next;
}Chunk;
typedef struct LString{
Chunk * head, * tail;
int curLen;
}LString;
串的基本操作
- 串赋值
- 串比较
- 求串长
- 串连接
- 求子串
- 串拷贝
- 串判空
- 清空串
- 子串的位置
- 串替换
- 子串插入
- 子串删除
- 串销毁
串的赋值
void StringAssign(SString* str, char ch[]) {
assert(str);
for (int i = 0; ch[i] != '\0'; ++i) {
str->ch[i] = ch[i];
str->ch[i+1] = '\0';
str->length = i;
}
}
串的比较
int StringIsEqual(SString S, SString T) {
int same = 1;
if (S.length != T.length) {
same = 0;
} else {
for (int i = 0; i < S.length; ++i) {
if (S.ch[i] != T.ch[i]) {
same = 0;
break;
}
same = 1;
}
}
return same;
}
串的链接
// 字符串链接,将一个串S紧接着放在另一个串T的后面,链接成一个新串
SString* StringConcat(SString* S, SString* T) {
assert(S);
assert(T);
SString *str = NULL;
str->length = S->length + T->length;
assert(MAXLEN >= str->length);
for (int i = 0; i < S->length; ++i) {
str->ch[i] = S->ch[i];
}
for (int i = 0; i < T->length; ++i) {
str->ch[S->length + i] = T->ch[i];
}
return str;
}
求子串
// 求子串,返回主串从第i开始长度为j的子串
SString* StringSub(SString* S, int i, int j) {
assert(S);
assert(i>=0 && i<=S->length);
assert(j >=0 && j+i-1 <= S->length);
SString* str = NULL;
str->length = 0;
for (int k = i - 1; k <i +j -1 ; ++k) {
str->ch[k-i+1] = S->ch[k];
}
str->length = j;
return str;
}
串的替换(需实现串的插入和删除操作)
// S从第i个开始插入T
void StringInsert(SString* S, SString* T, int i) {
assert(S);
assert(i>=0 && i<=S->length);
assert(S->length + T->length <= MAXLEN);
int z;
for ( z = S->length-1; z>=i-1; z--) {
S->ch[z + S->length] = S->ch[z];
}
for (int j = z+1; j < z+1+T->length; j++) {
S->ch[j] = T->ch[j-z-1];
}
}
// 删除从第i开始长度为j的子串
void StringDelete(SString* S, int i, int j) {
assert(S);
assert(i>=0 && i<=S->length);
assert(j >=0 && j+i-1 <= S->length);
for (int k = i+j-1; k < S->length ; ++k) {
S->ch[k-j] = S->ch[k];
}
S->length -= j;
}
// 主串S从第i个位置开始替换子串T
void StringReplace(SString* S, SString* T, int i) {
StringDelete(S, i, T->length);
StringInsert(S, T, i);
}
串的模式匹配算法
算法目的:确定主串中所含子串(模式串)第一次出现的位置(定位)
算法应用:搜索引擎,拼写检查,语言翻译,数据压缩
算法种类:简单匹配算法:BF算法(经典朴素,穷举);KMP算法(特点:速度快)
BF算法(Brute-Force)
算法思路就是从主串的每一个字符位置开始依次与子串的字符进行比较
匹配失败:主串开始i= 1;子串开始 j = 1;失败后主串回溯 i = i - j + 2;子串回溯 j = 1
匹配成功:返回 主串当前的 i - 子串长度
实现(顺序串)
int index_BF(SString S, SString T, int pos) {
int i = pos, j = 1;
while (i <= S.length && j <= T.length) {
if (S.ch[i] == T.ch[j]) { // 主串和子串依次匹配下一个字符
i++;
j++;
} else { // 匹配失败,主串子串回溯重新开始下一次匹配
i = i - j + 2;
j = 1;
}
}
if (j >= T.length) {
return i - T.length; // 返回匹配的第一个字符的下标
} else {
return 0; // 模式匹配不成功
}
}
平均算法时间复杂度O(N*M)
N为主串长度
M为字串长度
KMP算法
利用已经部分匹配的的结果而加快模式串的滑动速度
且主串S的指针i不回溯,可提速到O(n + m)。
算法思路
在BF算法基础上,主串S第i个字符与子串T第j个字符不匹配,则i不回溯继续++,j与i匹配失败时,需定义next[j],记录在子串中需要重新和主串中该字符进行比较的字符的位置。
next[j] = max{k|1<k<j,且从头开始的k-1个元素 = j前面的k-1个元素 当此集合为空时}
next[j] = 0 当j= 1时
next[j] = 1 其他情况
算法实现
void get_next(SString T) {
int i = 1;
T.next[1] = 0;
int j = 0;
while (i < T.length) {
if (j == 0 || T.ch[i] == T.ch[j]) {
i ++;
j ++;
T.next[i] = j;
} else {
j = T.next[j];
}
}
}
int index_KMP(SString S, SString T, int pos) {
get_next(T);
int i = pos, j = 1;
while (i <= S.length && j <= T.length) {
if (j == 0 || S.ch[i] == T.ch[j]) {
i ++;
j ++;
} else {
j = T.next[j];
}
}
if (j >= T.length) {
return i - T.length; // 返回匹配的第一个字符的下标
} else {
return 0; // 模式匹配不成功
}
}