数据结构与算法学习笔记(C语言)
上一篇文章学习了串的定长数组,这一章学习串的另外两种存储结构。堆分配存储表示(动态数组表示)和串的块链存储表示。
堆分配:在程序执行过程中,用malloc()函数在堆区申请所需大小的连续空间。
C语言描述如下
typedef struct {
char *ch; /*按照串长分配存储区*/
int length; /*串长*/
}String;
这种存储结构需要我们手动申请和释放空间。所以销毁串的操作需要实现
1.销毁串
void DestroyString(String *S)
{
S->length = 0;
free (S->ch);
S->ch = NULL;
}
2.在串S的第pos个位置之前插入串T
Status StrInsert(String *S, int pos, String T)
{
int i; char *new_ch;
if (pos < 1 || pos > S->length + 1) return ERROR;
/*如果T不是空串,那么为插入T重新分配空间*/
if (T.length) {
new_ch = (char *)malloc(S->ch, (S->length + T.length)sizeof(char));
if (!new_ch) exit(OVERFLOW);
ch = new_ch;
/*插入位置后面的元素后移,为T的插入留出位置*/
for (i = S->length - 1; i >= pos - 1; i--) {
S->ch[i + T.length] = S->ch[i];
}
/*插入T*/
for (i = pos - 1; i <= pos + T.length - 2; i++) {
S->ch[i] = T.ch[i + 1 - pos];
}
S->length += T.length;
}//if
return OK;
}
3.删除子串
Status DeleteStr(String *S, int pos, int len)
{
int i;
if (pos < 1 || pos > S->length || pos + len - 1 > S->length) return ERROR;
/*删除的位置不合理,或者没有这么长的子串能删除*/
for (i = pos - 1; i <= S->length - len - 1; i++) {
S->ch[i] = S->ch[i + len];
}
S->length -= len;
return OK;
}
串的其他操作已经在上一章用定长顺序存储结构实现了,自己对照着很容易写出来在堆分配顺序存储下的实现代码,这里就省略不写了。
测试串的删除和插入操作结果如下图:
如图所示,删除函数从串"hello, world! I am a C programmer!“中将子串”, world"删除,打印结果为"hello! I am a C programmer!",插入函数又将子串", world"插入,从而串恢复原来的串值!
串的块链存储结构
如上图所示,如果每个节点存储多个字符,不利于对子串的操作,如果每个节点存储一个字符,那么指针域就占据了一半的存储空间,所以串这种数据结构实在不适合链式存储。
C语言描述如下
#define CHUNKSIZE 80
typedef struct Chunk {
char ch[Chunk];
struct Chunk *next;
}Chunk;
typedef struct {
Chunk *head, *tail; /*串的头尾指针*/
int curlen; /*串的当前长度*/
}String;
链表的各种操作之前早已经学过,串的链式存储结构和链表的唯一不同在于节点里面数据域存储的数据为字符,因此关于串的链式存储结构的相关操作就不再赘述。
对串的总结:串是一种数据元素值限定为字符的线性表,并且串也有两种存储结构,顺序存储结构和链式存储结构。对串的操作通常将串作为一个整体考虑,极少操作单个字符
之前我们在学习线性表的时候,对于顺序存储结构用的是动态数组,也就是在堆区申请空间,相应地,也可以定义一个定长数组,让编译器在栈区分配连续空间,不过如果要插入元素,到达数组的最大存储空间的话,后面的插入操作会失败。
下一章,我们将学习一种难理解,但是很高效的求字串位置的算法——KMP模式匹配算法。