数据结构——串

一、前言


串的实质是一种特殊的线性表,其数据元素的类型固定为字符型,不像在前面学习的链表、顺序表、双指针链表、双指针循环链表、栈(从栈顶输出和输入)、队列(允许从表尾输入,表头输出)而串这种数据结构常应用于信息检索、文本编辑等领域具有广泛的用途。接下来我们就研究串的定义和存储结构、基本操作相关内容,让我们一起进入学习!

二、串的定义:

1、字符串:字符串是由零个或多个字符组成的有限序列。通常在直接写串的字符组成时,字符串有多个字符组成的话采用双引号,而单个字符的话采用单引号括起来即可。(注意:引号是顶界符,它不属于串,其作用是避免串值与变量名或常量混淆)

2、子串:串中任意连续的字符组成的子序列称为该串的子串。

3、主串:包含子串的串称为主串。子串是包含在主串的一段字符序列。

4、串相等:当两个串的长度和对应位置的单个字符相等时,两个串才相等。

三、串的存储结构:


1、定长顺序串:

1)定长顺序串的存储结构类似于我们在前面学习的线性表的顺序存储结构,在这个结构中的内存是提前申请的固定内存,在后续的添加相关数据时,是不用重新申请内存空间。但是当数据元素的存储量超过固定内存空间时,该系统将会报错。(该存储结构的特点也称为静态结构类型)在内存空间中的地址也是连续的存储空间。

2)具体操作:需要提前申请内存空间,下面是相应的代码定义其相关的结构体。

//串——定长存储结构
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<string.h>
//定义字符串数组的长度
#define STRINGMAX 100
typedef char ElemType ;     //定义数据元素类型
//定义串的定长结构体
typedef struct {
    ElemType VES[STRINGMAX] ;
    int len ;
}Str ;

(1)判断串是否为空串的相关思想:通过结构体中定义的相关变量len,倘若其为0则返回true,反之返回false。代码如下:

//操作前提:串S存在
//操作结果:若串S为空串,则返回true,否则返回false
bool StrEmpty(Str *S){
    if (S->len == 0) {
        return true ;
    }else{
        return false ;
    }
}

(2)将串赋值为相关值的串函数:首先要判断进行赋值的串长度是否超过相应串的定长,若超过则要返回错误,不能继续赋值。反之进行以下操作:先将要进行赋值的串的长度赋值给结构体中的len 变量,然后再通过一个for循环依次赋值。代码如下:

//操作前提:chars是字符串常量
//操作结果:生成一个值等于chars的串S
bool StrAssign(Str *S , ElemType *chars){
    if (strlen(chars)>STRINGMAX) {
        printf("\n\t\t长度不符合要求\n");
        return false ;
    }else{
        S->len =(int)strlen(chars);     //将串长度赋值给len变量;
        for (int i = 0; i<S->len; i++) {
            S->VES[i] = *(chars+i) ;    //此处将chars串的元素逐一赋值给相关的串,此代码中数组下标为0的位置不存放串长,所以该位置的空间存放串的字符值
        }
        return true ;
    }
}

(3)比较串大小思想:首先通过一个for循环遍历判断相应位置的字符值,通过比较其对应的字符值是否相等,倘若相等则返回其对应字符的ASILL值的相减值的大小即可判断其两串的大小。倘若不相等的则继续循环,直到遍历数达到两个串中的一个串长度即可结束循环,最后再判断其对应的长度大小。代码如下:

//比较串大小的函数
int StrCompare(Str *S , Str *T){
    int i ;     //用来组成串S的循环
    for(i = 0 ; i<S->len&&i<S->len ; i++){      //这里循环判断该两串的固定长度下,字符是否相等
        if (S->VES[i] != T->VES[i]) {
            return (S->VES[i]-T->VES[i]) ;      //当字符不相等时,则拿对应数组下标的字符的ASCII值进行相减,则可以得到相应的大小
        }
    }
    return (S->len - T->len) ;      //当上面的循环退出后,则比较两个串的长度,则能得出两串的大小关系
}

(4)显示串函数思想:首先判断此串是否为空串,若为空串则输出提示并且返回false。反之则将此串通过循环依次输出,输出完毕后返回true标志符。代码如下:

//显示字串的函数
bool XianShi(Str *S){
    if (S->len==0) {        //首先判断此串是否为空串
        printf("\n\t\t此串为空串");
        return false ;
    }else{                  //倘若不是空串,则将串中各个值输出
        printf("\n\t\t该串元素有:");
        for (int i = 0 ; i<S->len ; i++) {
            printf("%2c",S->VES[i]);
        }
        return true ;
    }
}

(5)连接两串函数思想:首次判断两串连接之后内存空间是否充足,倘若空间充足则进行以下操作,首先将通过将原来的串通过for循环(结束条件为:超过串的长度则循环结束)将串中的元素依次放入新的串中。接着再次通过for循环将需要连接的串放入新的串中,在原串已经放入的地址位置开始,依次放入需要连接串中的字符到新串中,最后将新串的长度改为原串和连接串长度之和。反之若两串的长度之和大于内存空间中固定的值时,则会有截断字符现象(即在原串的基础上,连接串没有完全连接到新串中,只连接部分字符)最后返回true。代码如下:

//连接字串
bool StrCat(Str *V , Str *S , Str *T){
    if ((S->len+T->len)<STRINGMAX) {        //此情况是内存空间充足未截断的情况
        for (int i = 0; i<S->len ; i++) {   //此处循环为将S串中的字符赋予到新建立的串上
            V->VES[i] = S->VES[i];
        }
        for (int j =0 ; j<T->len ; j++) {   //此处为第二段循环将T串继续接在新建立的串上面
            V->VES[S->len + j] = T->VES[j];
        }
        V->len = S->len+T->len ;
    }else{
        printf("\n\t\t连接后的串大于固定内存空间有截断的情况\n");
        T->len = STRINGMAX - S->len ;       //将最大内存空间减去S的空间即可得到剩余的空间即是T串能赋值到新串上面的空间
        for (int i = 0; i<S->len ; i++) {
            V->VES[i] = S->VES[i] ;
        }
        for (int j = 0; j<T->len ; j++) {
            V->VES[S->len + j] = T->VES[j] ;
        }
        V->len = STRINGMAX ;
    }
    return true ;
}

(6)取出子串函数思想:首先判断串是否为空串,倘如是空串则无法取子串,给予提醒并且返回false错误。倘若不是空串,则首先要判断取子串是否合理(pos为取子串的初始位置),如果pos小于1或者大于串的长度则取子串的位置不合理,接着判断取子串是否合理,若取子串的长度大于从pos位置到串长度结束位置,则取子串的长度不合理。反之上述条件都合理,则将pos位置开始输出到len长度后结束。代码如下:

//取出子串
//操作结果:从pos位置开始往后取len长度的子串
bool SubString(Str *S ,int pos, int len){
    if (S->len==0) {                            //判断此串是否为空串
        printf("\n\t\t此串为空串无法取其子串\n");
        return false ;
    }
    if (pos<1||pos>S->len||len<1||pos>S->len-len+1) {   //判断取子串的位置是否合理和子串的长度是否合理
        printf("\n\t\t取子串位置不合理\n");
        return false ;
    }
    printf("\n\t\t取出子串为:");
    for (int i = 0; i<len ; i++) {
        printf("%2c",S->VES[pos-1 + i]);
    }
    return true ;
}

(7)删除串函数的思想:首次判断串是否为空串,反之不是空串的话,接着判断此串删除位置和删除长度是否合理,具体思想如上面所述的取子串的思想一样。接下来具体讲解一下删除位置是否为串最后一个字符的位置,倘若不是最后一个位置,则将删除位置并且根据对应长度得到删除后串的后面剩余字符,通过for循环依次向前移动到前面删除位置处,在移动时则要考虑移动元素的多少;则在最后将串的长度改为串删除后的长度。如果删除位置为串的最后一个字符只需要将串长度减1即可,无需大量移动元素。(因为这里的类似于顺序表的结构,删除只是形式上删除,在内存空间中始终存在被删除的元素值,只是无法访问而且)。代码如下:

//删除子串
//条件:首先判断此串是否为空串,删除位置和删除长度是否合理
//操作思路:首先判断相关条件,接着将pos位置往后len长度的串进行删除,接着将其后面的元素往前移,类似于线性表
//操作结果:将相关子串进行删除
bool StrDelete(Str *S , int pos ,int len ){
    if (S->len==0) {                            //此处判断此串是否为空
        printf("\n\t\t此串为空串,无法进行删除\n");
        return false ;
    }
    if (pos<0||pos>S->len||len<1||pos>S->len-len+1) {//此处判断删除位置是否合理
        printf("\n\t\t删除子串的位置不合理\n");
        return false ;
    }
    if (pos-1+len!=S->len) {                       //当删除结束位置并不是最后一个元素位置时
        for (int i = pos-1; i<pos-1+len; i++) {    //将后面剩余的串字符元素依次赋值到前面pos位置,依次往后
            if (S->VES[i+len]=='\0') {             //判断赋值是否结束,若结束则修改其删除后的串长度即可
                int count ;                        //若没有结束则继续进行以下的赋值循环
                count = i-(pos-1);
                S->len = (pos-1)+count ;
            }
            S->VES[i] = S->VES[i+len];             //该处为赋值循环
        }
        return true ;
    }else{                                         //此处为删除结束位置刚刚好在最后一个元素,则只要修改串长度即可
        S->len = pos -1 ;
        return true ;
    }
}

(8)插入串函数思想:首先判断插入串的位置是否合理(插入位置不能小于1和插入位置只能连接着最后一个字符,与原串最后一个字符相隔一个字符)。倘若插入位置合理的话,接着要判断插入串的长度放到原串中的长度是否大于内存空间,这个关乎到是否可以完全插入。1)我们先来讨论能完全插入的情况:开始通过for循环将pos位置以后的元素依次移动插入串长度个单位。接着将插入串通过for循环从pos位置开始放入子串。最后将串的长度改为插入后的长度即可。2)接着在内存空间不够的基础上面再进行判断:该判断的是插入位置是否是原本串的最后一个的字符后面的位置和判断插入串T后原来串后面的字符是否截断(实质是判断插入位置是否将原来串后面的元素覆盖掉)这里的情况是覆盖掉的情况操作,只需要将插入串的元素依次放入原串中即可,结束条件是最大内存空间。3)以下情况是插入串后,后面存在剩余空间,则需要将pos位置的元素放到后面,直到达到最大内存空间则停止。最后改变其相应长度为内存空间的最大值;代码如下:

//插入子串
//插入条件:插入操作前首先判断其插入的串与原有的串的长度之和是否超过字符数组的最大值
//操作结果:分为完全插入和截断式插入
bool StrInsert(Str *S , int pos , Str *T){
    if (pos<1||pos>S->len) {
        printf("\n\t\t插入位置不合理\n");
        return false ;
    }
    if ((S->len+T->len)<=STRINGMAX) {       //存储空间充足,可完全插入
        for (int i = S->len-1; i>=pos - 1; i--) {       //该处为将pos位置以后的元素都向后移动T->len长度个单位
            S->VES[i + T->len] = S->VES[i] ;
        }
        for (int j = 0; j< T->len; j++) {               //将值放进需要插入的串中
            S->VES[j + pos - 1] = T->VES[j] ;
        }
        S->len = S->len + T->len ;      //改变为插入后串的长度
        return true ;
    }else if (pos==S->len||(STRINGMAX - pos + 1)<=T->len){         //内存空间不足时,第一种情况为pos位置最后一个元素的后面
        //还有内存空间不足,但其T中的值未能完全存放时
        for (int i = 0; i<STRINGMAX - pos; i++) {
            S->VES[i + pos] = T->VES[i];
        }
        S->len = STRINGMAX ;
        return false ;      //返回该值说明未完全插入
    }else{
        int count ;     //记录剩余的pos往后的字符能存储多少个
        count = STRINGMAX - pos + 1 - T->len ;
        for (int i = 0; i<count; i++) {                 //将pos处和其后面的对应长度的字符数据放到后面
            S->VES[STRINGMAX - pos +1 + i] = S->VES[pos - 1 + i] ;
        }
        for (int j = 0; j < T->len; j++) {
            S->VES[pos - 1 +j] = T->VES[j] ;
        }
        S->len = STRINGMAX ;
        return false ;
    }
}

(9)串的查找思想(暴力算法):首先通过pos位置开始判断是否与相应位置的字符相等,倘若相等则将两个串的字符位置依次向后移动一位继续比较期大小,如果接下来不相等,则将主串的位置继续移动到下一位,而模式串则需要返回第一个字符的位置,继续进行比较。这个算法时间复杂度较大,根据具体情况具体讨论;

代码如下:

//查找子串:朴素匹配算法:暴力算法
//算法思想:首先由pos位置开始匹配,如果匹配第一个字符数据未相等呢,则主串中的匹配位置向后移动一位,接着将模块串的位置恢复原来的第一位重新开始匹配,倘若相等,则主串和模块串都向前移动一位。直到匹配成功或匹配数据结束
int StrIndex(Str *S , int pos , Str *T ){
    int count = pos -1 ;         //用来记录主串开始循环比较的初始位置
    int i , j ;
        for (i = pos - 1, j = 0; i<S->len&& j<T->len ;) {
            if (S->VES[i]!=T->VES[j]) {
                i = count + 1 ;
                count = i ;
                j = 0 ;
            }else{
                count+=1;
                i++ ;
                j++ ;
            }
        }
    if (i>S->len) {
        return -1 ;
    }else{
        return count-1 ;
    }
}

四、串的总结:


串的存储结构中我只讲了其中一种,但是算法思想三个都差不多一样的。其三个存储结构是两个顺序结构。1)其结构为静态内存空间,其需要提前申请内存空间,其内存空间是固定的。2)其结构也是顺序结构,但是内存空间动态申请的,但是其内存大小是固定。3)最后一种是链式结构,内存空间需要动态申请的。

  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值