Head First 串

昨天刚学了串这一章,今天趁热在自己的学习记录里记下来好了,数据结构第三篇:串的操作与应用。和以前一样,参考严蔚敏版《数据结构》。同时,将串的匹配(重点是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 80

typedef 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值