Head First 串

来源:互联网 发布:apache hadoop yarn 编辑:程序博客网 时间:2024/06/06 05:48

昨天刚学了串这一章,今天趁热在自己的学习记录里记下来好了,数据结构第三篇:串的操作与应用。和以前一样,参考严蔚敏版《数据结构》。同时,将串的匹配(重点是KMP匹配算法)单独拿出来作为一篇博文,毕竟这个比较复杂。其实主要是为了充数偷笑


目录:

一、串的概述

二、定长顺序串的基本操作

三、堆分配串的基本操作


一、串的概述与基本操作


串说白了就是我们在很多高级语言中用到的字符串类型,这并不是基本数据类型,但这些高级语言已经将对串的操作封装的很全、很好了。所以我们用起来也比较方便。其实完全可以自己去试着简单实现一下那些方法。这是一个非常好的练习。

对串来说,我们一般就三种存储方式:定长顺序存储、堆分配存储、块链存储。

定长顺序存储:串的顺序存储方式,利用一块连续的内存空间来存储字符序列,这段内存空间的长度固定,可用宏来表示,整个串采用顺序数组存储,同时规定数组的第一个元素(0号下标)为串的长度,便于以后的操作。

常量与数据结构定义:

#define MAX_STRING_LENGTH 255                           //串的最大串长typedef unsigned char SString[MAX_STRING_LENGTH + 1];   //0号单元存放串的长度

仅仅使用一个数组就可以代表一个串,如前文所述,将数组第一个单元(0号位置)设为串的长度,不用再定义新的变量。


堆分配存储:串的动态存储方法。设置一个字符指针代表串的内容,增设一个变量存储串的长度方便以后的操作。堆分配存储最大的特点就是灵活、动态。这里不会出现字符串的“截断”现象,当空间不够时重做分配,记录新空间基址即可。

/** *  串的堆分配存储表示 */typedef struct {    char *ch;       //若是非空串,则按串长分配存储区,否则为NULL    int length;     //串长}HString;
对堆分配存储的串进行操作时,由于串的存储空间是在堆上的,需要我们自己管理,例如在串复制时需要首先释放原串的空间,再重新申请一块空间进行操作。


块链存储:串的链式存储。由于串结构的数据域仅为一个字符(1字节),如果为每个结点加一个指针域(4字节),则显得有些浪费空间。因此在采用链式存储时往往采用”块链“——每个结点存放多个字符。这样当串长不是结点大小的整数倍时,最后一个结点由特殊字符(例如‘#’)补满。由于连式存储较前两者而言并无太大优势,加之操作复杂,所以不做要求。

顺带一提,对于每个结点存放字符的数目,有一个概念叫做“存储密度”。存储密度 = 串值所占的存储位 / 实际分配的存储位。该值越小块链处理起来就越方便。

块链的数据结构定义:

#define CHUNK_SIZE 80typedef struct Chunk{    char ch[CHUNK_SIZE];    //数据域    struct Chunk *next;     //指针域}Chunk;typedef struct {    Chunk *head;    //串的头指针    Chunk *tail;    //串的尾指针    int curlen;     //串的当前长度}LString;

二、定长顺序串的基本操作

串的基本操作我们都比较熟悉,例如StrCopy和StrCompare等。在这里提一下,关于串面试时的考点还是挺多的。例如:while (*s++ = *t++);  的作用?(赋值字符串),自己编写strcmp函数?等等。掌握这些基本操作对于应试非常有帮助。

先看一下定长顺序串的所有基本操作:

/** *  根据字符串常量生成串T * *  @param T          生成的串 *  @param constChars 字符串常量 */void StrAssign(SString *T, char constChars[]);/** *  由串S复制得串T */void StrCopy(SString *T, SString S);/** *  若S为空串,返回TRUE,否则返回FALSE */BOOL StrEmpty(SString S);/** *  返回串S的长度 */int StrLen(SString S);/** *  将S清为空串 */void ClearString(SString *S);/** *  用T返回将S1和S2连接成的新串 */void Concat(SString *T, SString S1, SString S2);/** *  用Sub返回串S中第pos个字符起长度为len的字串 */void SubString(SString *Sub, SString S, int pos, int len);/** *  返回在主串S中第pos个字符之后第一次出现字串T的位置,若不存在字串T,则返回0。(朴素匹配算法) */int Index(SString S, SString T, int pos);/** *  用V替换主串S中得所有与T相等的不重叠的字串 */void Replace(SString *S, SString T, SString V);/** *  在串S的第pos个位置之前插入串T */void StrInsert(SString *S, int pos, SString T);/** *  从串S中删除第pos个字符起长度为len的字串 */void StrDelete(SString *S, int pos, int len);/** *  销毁串S */void DestroyString(SString *S);/** *  比较亮字符串 * *  @param S 第一个字符串 *  @param T 第二个字符串 * *  @return 结果为1说明S>T,结果为-1说明S<T,结果为0说明S=T */int StrCompare(SString S, SString T);/** *  打印串S * *  @param S 要打印的串 */void PrintStr(SString S);

其中子串的定位函数Index即为串的模式匹配算法之一,关于串的模式匹配,单独总结。这里将其他的操作给出实现

void StrAssign(SString *T, char constChars[]){    int i = 1, j = 0;    while (constChars[j] != '\0') {        (*T)[i++] = constChars[j++];    }    (*T)[0] = j;}void StrCopy(SString *T, SString S){    int i = 1, j = 1;    while (S[j] != '\0') {        (*T)[i++] = S[j++];    }    (*T)[0] = S[0];}BOOL StrEmpty(SString S){    return (S[0] > 0) ? FALSE : TRUE;}int StrLen(SString S){    return S[0];}void ClearString(SString *S){    (*S)[1] = '\0';    (*S)[0] = 0;}void Concat(SString *T, SString S1, SString S2){    int s1_len = S1[0];    int s2_len = S2[0];        int k = 1;        if (s1_len + s2_len <= MAX_STRING_LENGTH) {     //未被截断        for (int i = 1; i <= s1_len; i++) {            (*T)[k++] = S1[i];        }        for (int j = 1; j <= s2_len; j++) {            (*T)[k++] = S2[j];        }        (*T)[0] = s1_len + s2_len;    } else if (s1_len < MAX_STRING_LENGTH) {        //S2被截断        for (int i = 0; i < s1_len; i++) {            (*T)[k++] = S1[i];        }        for (int j = 0; j < MAX_STRING_LENGTH - s1_len; j++) {            (*T)[k++] = S2[j];        }        (*T)[0] = MAX_STRING_LENGTH;    } else {                                        //仅取S1        for (int i = 0; i < MAX_STRING_LENGTH; i++) {            (*T)[k++] = S1[i];        }        (*T)[0] = MAX_STRING_LENGTH;    }}void SubString(SString *Sub, SString S, int pos, int len){    if (pos < 1 || pos > S[0] || len < 0 || len > S[0] - pos + 1) {        (*Sub)[0] = '\0';        return;    }        int k = 1;    for (int i = pos; i < pos + len; i++) {        (*Sub)[k++] = S[i];    }    (*Sub)[0] = len;}void Replace(SString *S, SString T, SString V){    if (T[0] != V[0]) {        return;    }        int pos = 0;    while ((pos = Index(*S, T, 1)) > 0) {        int k = 1;        for (int i = pos; i < pos + T[0]; i++) {            (*S)[i] = V[k++];        }    }}void StrInsert(SString *S, int pos, SString T){    if (pos < 1 || pos > (*S)[0] || (*S)[0] + T[0] > MAX_STRING_LENGTH) {        return;    }        int t_len = T[0];        for (int i = (*S)[0]; i >= pos; i--) {        (*S)[i + t_len] = (*S)[i];    }        int k = 1;    for (int i = pos; i < pos + t_len; i++) {        (*S)[i] = T[k++];    }        (*S)[0] += T[0];}void StrDelete(SString *S, int pos, int len){    if (pos < 1 || pos + len > (*S)[0]) {        return;    }        for (int i = pos; i < len + pos; i++) {        (*S)[i] = (*S)[i + len];    }        (*S)[0] -= len;}void DestroyString(SString *S){    free(S);}int StrCompare(SString S, SString T){    for (int i = 1; i <= S[0] && i <= T[0]; i++) {                if (S[i] == T[i]) {            continue;        } else if (S[i] < T[i]) {            return 1;        } else {            return -1;        }            }        if (S[0] == T[0]) {        return 0;    } else if (S[0] < T[0]) {        return 1;    } else {        return -1;    }}void PrintStr(SString S){    printf("SString = ");    for (int i = 1; i <= S[0]; i++) {        printf("%c", S[i]);    }    printf("  length = %d", S[0]);    printf("\n");}

由于定长顺序串的数组0元素位置为串长,所以在循环处理时应该注意下标值的范围:1 ≤ i ≤ S[0]。


三、堆分配串的基本操作


对比定长顺序串,可以看到堆分配串的灵活性以及方便操作性。

堆分配串的基本操作:

/** *  在串S的第pos个字符之前插入串T * *  @return 若正确插入,则返回1,否则返回0 */Status HStrInsert(HString *S, int pos, HString T);/** *  删除串S的第pos个位置的字符开始len长度的字串 * *  @param S   待删除的主串 *  @param pos 待删除字串在主串的位置 *  @param len 待删除字串的长度 * *  @return 若成功删除,则返回1,否则返回0 */Status HStrDelete(HString *S, int pos, int len);/** *  生成一个其值等于串常量chars的串T * *  @return 若成功生成串T,则返回1,否则返回0 */Status HStrAssign(HString *T, char *chars);/** *  返回S的元素个数,即S串的长度 * *  @return 串S的长度 */int HStrLength(HString S);/** *  按字典顺序比较两个串 * *  @param S 第一个串 *  @param T 第二个串 * *  @return 若S>T,则返回值大于零,若S=T,则返回值等于零,否则返回值小于零。 */int HStrCompare(HString S, HString T);/** *  清空串S */Status ClearHString(HString *S);/** *  用T返回由S1和S2联接而成的新串 * *  @param T  联接成的新串 *  @param S1 待联接的第一个串 *  @param S2 待联接的第二个串 * *  @return 联接成功时返回1,否则返回0 */Status ConcatHString(HString *T, HString S1, HString S2);/** *  返回串S的第pos个字符起长度为len的子串 * *  @param Sub 生成的字串 *  @param S   传入的串 *  @param pos 子串在主串中的起始位置 *  @param len 字串的长度 * *  @return 若操作成功则返回1,否则返回0 */Status SubHString(HString *Sub, HString S, int pos, int len);/** *  打印串S */void PrintHString(HString S);

以及实现:

Status HStrInsert(HString *S, int pos, HString T){    if (pos < 1 || pos > (*S).length + 1) {        return ERROR;    }        //如果T非空,则重新为S分配空间    if (T.length) {        if (!((*S).ch = (char *)realloc((*S).ch, ((*S).length + T.length) * sizeof(char)))) {            exit(OVERFLOW);        }                //插入位置后面的所有元素后移        for (int i = (*S).length - 1; i >= pos - 1; i--) {            (*S).ch[i + T.length] = (*S).ch[i];        }                //插入T        for (int i = 0; i < T.length; i++) {            (*S).ch[i + pos - 1] = T.ch[i];        }        (*S).length += T.length;    }        return OK;}Status HStrDelete(HString *S, int pos, int len){    if (pos < 1 || pos > (*S).length || len < 0 || len > (*S).length - pos + 1) {        return ERROR;    }        if (len) {        //元素前移        for (int i = pos - 1; i < (*S).length - len + 1; i++) {            (*S).ch[i] = (*S).ch[i + len];        }                //长度减少        (*S).length -= len;    }        return OK;}Status HStrAssign(HString *T, char *chars){    //如果T本来不为空,则释放内存空间    if ((*T).ch) {        free((*T).ch);    }        //求chars的长度    int count = 0;    for (char *c = chars; *c; c++, count++);        if (count == 0) {        (*T).ch = NULL;        (*T).length = 0;    } else {        if (!((*T).ch = (char *)malloc(sizeof(char) * count))) {            exit(OVERFLOW);        }                for (int i = 0; i < count; i++) {            (*T).ch[i] = chars[i];        }        (*T).length = count;    }        return OK;}int HStrLength(HString S){    return S.length;}int HStrCompare(HString S, HString T){    for (int i = 0; i < S.length && i < T.length; i++) {        if (S.ch[i] != T.ch[i]) {            return S.ch[i] - T.ch[i];        }    }        return S.length - T.length;}Status ClearHString(HString *S){    if ((*S).ch) {        free((*S).ch);        (*S).ch = NULL;    }        (*S).length = 0;        return OK;}Status ConcatHString(HString *T, HString S1, HString S2){    //释放旧空间    if ((*T).ch) {        free((*T).ch);    }        if (!((*T).ch = (char *)malloc((S1.length + S2.length) * sizeof(char)))) {        exit(OVERFLOW);    }        for (int i = 0; i < S1.length; i++) {        (*T).ch[i] = S1.ch[i];    }    for (int i = 0; i < S2.length; i++) {        (*T).ch[i + S1.length] = S2.ch[i];    }    (*T).length = S1.length + S2.length;        return OK;}Status SubHString(HString *Sub, HString S, int pos, int len){    if (pos < 1 || pos > S.length || len < 0 || len > S.length - pos + 1) {        return ERROR;    }        if ((*Sub).ch) {        free((*Sub).ch);    }        if (len == 0) {        (*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[i + pos - 1];        }        (*Sub).length = len;    }        return OK;}void PrintHString(HString S){    printf("HString = ");    for (int i = 0; i < S.length; i++) {        printf("%c", S.ch[i]);    }    printf("\tlength = %d\n", S.length);}

对于堆分配串的注意事项:不要忘记释放原来的空间,对于malloc的空间,如果不手动释放就会造成内存泄露。另外,与定长顺序串不同,堆分配串是从数组下标为0开始就存储数据的,所以在操作循环的下标时应该注意一下。


之前也说过,串的块链表示并不常用,所以在此就不作实现了。

0 0