数据结构与算法——串

🚀前言


😊各位小伙伴久等了,本专栏新文章出炉了!!!

在这里插入图片描述

计算机上的非数值处理的对象基本上是字符串数据,字符串一般简称为是计算机中一种常见且重要的数据结构。

在不同数据类型的应用中,所处理的字符串具有不同的特点,要有效的实现字符串的处理,就必须根据具体情况使用合适的存储结构。


🚀串类型的定义


串(String)(或字符串)是由零个或多个字符组成的有限序列,一般记为 S = ′ a 1 , a 2 , a 3 , . . . , a n ′ ( n > = 0 ) S='a1, a2, a3,...,an'(n>=0) S=a1,a2,a3,...,an(n>=0),其中,s是串的名,用单引号括起来的字符序列是串的值; a i ( 1 < = i < = n ) ai(1<=i<=n) ai1<=i<=n可以是字母,数字或其他字符;串中字符的数目n称为串的长度,零个字符的串称为空串(null String),表示为 t = “” t=“ ” t=“”,它的长度为零,串的逻辑结构属于线性结构。

在这里插入图片描述

📌例如:S=“holleworld!”

由一个或多个空格字符组成的串,称为空格串(blank string,请注意,此处不是空串),它的长度为串中空格字符的个数

串中任意个连续的字符组成的子序列称为该串的子串,包含子串的串相应地称为主串,空串是任意非空字符串的子串,通常称字符在序列中的序号为该字符在串中的位置,子串在主串中的位置则以子串的第一个字符在主串中的位置来表示

想要称两个串是相等的,只有当且仅当这两个串的值相等,那么这两个串才是相等的,也就是说,只有当两个串的长度相等,并且各个对应位置的字符都相等时才相等

值得一提的是,串值必须用一对单引号(双引号)括起来,但单引号本身不属于串,它的作用是为了避免与变量名或数的常量混淆而已

串的逻辑结构和线性表极为相似,区别仅在于串的数据对象约束为字符集,然而,串的基本操作和线性表由很大差别。在线性表的基本操作中,大多以”单个元素“作为操作对象,例如在线性表中查找某个元素,求取某个元素,在某个位置上插入一个元素和删除一个元素等,而在串的基本操作中,通常以”串的整体“作为操作对象,例如在串中查找某一个子串,求取一个子串,在串中的某个位置上插入一个子串以及删除一个子串


串与线性表的区别:

  • 📌串是限定了元素为字符的线性表
  • 📌线性表的操作主要针对表内的某一个元素,而串操作主要针对串内的一个子串(整体)
  • 📌在串里面,一个数据元素是由一个字符组成

🚀串的存储结构以及实现


串有3中机内表示方法,分别介绍如下:


🚢定长顺序存储表示

类似于线性表的顺序存储结构,用一组地址连续的存储单元存储串值的字符序列,在串的顺序存储结构中,按照预定义的大小,为每个定义的串分量分配一个固定长度的存储区,可用于定长数组描述

#define MAXLEN 255      //预定义最大串长为255
typedef struct{
    char ch[MAXLEN];        //每个分量存储一个字符
    int length;             //串的长度
}SString;

串的实际长度可在这预定义长度的范围内随意,超过预定义长度的串值则被舍去,称之为“截断”。对串长有两种方法表示:一种是如上定义描述的那样,以下标为0的数组分量存放串的实际长度,另一种是在串值后面加一个不计入串长的结束标记字符,此时的串长为隐含值,显然不便于进行某些串的操作。


🌈串联接Concat(&T, S1, S2)

假设 S 1 , S 2 S1,S2 S1S2 T T T都是String型的串变量,且串T是由串S1联杰结串S2得到的,即串T的值的前一段和串S1的值相等,串T的值的后一段和串S2的值相等,则只要进行相应的“串值复制”操作即可,只是需前述约定,对超长部分实施“截断”操作


🌈求子串SubString(& Sub, S, pos, len)

求子串的过程即为复制字符序列的过程,将串S中从第pos个字符开始长度为len的字符序列赋值到串Sub中。显然,本操作不会有需截断的情况,但有可能产生用户给出的参数不符合操作的初始条件,返回false

bool SubString(SString &Sub, SString S, int pos, int len){
    //子串范围越界
    if(pos+len-1 > S.length){
        return false;
    }
    for(int i = pos; i<pos+len; i++){
        Sub.ch[i-pos+1] = S.ch[i];
    }
    Sub.length = len;
    return true;
}

在顺序存储结构中,实现串操作的原操作为“字符序列的复制”,操作的时间复杂度基于复制的字符序列的长度,另一操作的特点是:如果在操作中出现串值序列的长度超过上界MAXSTRLEN时,约定用截尾法处理。这种情况不仅在求联串时可能发生,在串的其他操作中,如插入、置换等也可能发生。客服这个弊病唯有不限定串长的最大长度,即动态分配串值的存储空间。


🌈比较操作

若S>T,则返回值>0;若S=T,则返回值=0;若S<T,则返回值<0

//比较操作,若S>T,则返回值>0;若S=T,则返回值=0;若S<T,则返回值<0。
int StrCompare(SString S, SString T){
    for(int i=1; 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;
}

🌈定位操作

若主串S中存在与串T值相同的子串,则返回它在主串S中第一次出现的位置,否则函数值为0。

int Index(SString S, SString T){
    int i = 1, n = StrLength(s), m = StrLength(T);
    SString sub;        //用于暂存子串
    while(i<=n-m+1){
        SubStriing(sub, S, i, m);
        if(StrCompare(sub, T) != 0){
            i++;
        }else{
            return i;       //返回子串在主串中的位置
        }
    }
    return 0;       //S中不存在与T相等的子串
}

🚢堆分配存储表示

这种存储表示的特点是,仍以一组地址连续的存储单元存放串值字符序列,但他们的存储空间是在程序执行过程中动态分配而得。利用函数malloc()为每个新产生的串分配一块实际串长所需要的存储空间,若分配成功,则返回一个指向起始地址的指针,作为串的基址,同时,为了以后处理方便,约定串长作为存储结构的一部分

#define MAXLEN 255      //预定义最大串长为255
typedef struct{
    char *ch;           //按串长分配存储区,ch指向串的基地址
    int length;         //串的长度
}HString;

HString S;
S.ch = (char *)malloc(MAXLEN * sizeof(char));
S.length = 0;

这种存储结构表示时的串操作仍是基于“字符序列的复制”进行的。

由于堆分配存储结构的串既有顺序存储结构的特点,处理方便,操作中对串长又没有任何限制,更显灵活,因此在串处理的应用程序中也常被选用


🚢链式存储表示

和线性表的链式存储结构相类似,可采用链表方式存储串值,由于串结构的特殊性——结构中的每个数据是一个字符,则用链表存储串值时,存在一个“结点大小”的问题,即每个结点可以存放一个字符,也可以存放多个字符。当结点大小大于1时,由于串长不一定是结点大小的整数倍,则链表中的最后一个结点不一定全被串值占满,此时通常补上“#”或者其他非串值字符(通常“#”不属于串的字符集,是一个特殊的符号

第一种表示:

在这里插入图片描述

typedef struct StringNode{
    char ch;        //每个节点存一个字符
    struct StringNode * next;
}StringNode, * String;

为了便于进行串的操作,当以链表存储串值时,除头指针外还可附设一个尾指针表示链表中的最后一个结点,并给出当前串的长度。称如此定义的串存储结构为块链结构

第二种表示:

在这里插入图片描述

typedef struct StringNode{
    char ch[4];        //每个节点存多个字符
    struct StringNode * next;
}StringNode, * String;

由于在一般情况下,对串进行操作时,只需要从头向尾扫描即可,则对串值不必建立双向链表。设尾指针的目的是为了便于进行联结操作,但应注意联结时需要处理第一个串尾的无效字符

在链式存储方式中,结点大小的选择和顺序存储方式的格式选择一样都很重要,它直接影响着串处理的效率

存储密度可定义为:串值所占的存储位/实际分配的存储位

存储密度小(如结点大小为1时),运算处理方便,然而,存储占用最大,如果在串处理过程中需要进行内、外存交换的话,则会因为内外存交换操作过多而影响处理的总效率。一般地,字符集小,则字符的机内编码就短,这也影响串值的存储方式的选取。

串值的链式存储结构对某些串操作,如联接操作等有一定方便之处,但总的来说不如另两种存储结构灵活,它占用存储量大且操作复杂。


🚀拓展


🚢朴素模式匹配算法

串的模式匹配:在主串中找到与匹配模式相同的子串,并返回其所在位置。

朴素模式匹配算法:
将主串中与模式串长度相同的子串提出来,一个一个与模式串进行比较,当子串与模式串某个对应字符不匹配时,就立即放弃当前子串,转而检索下一个子串。

int Index(SString S, SString T){
    int k=1;
    int i=k, j=1;
    while(i<=S.length && j<=T.length){
        if(S.ch[i] == T.ch[i]){
            ++i;
            ++j;        //继续比较后继字符
        }else{
            k++;        //检查下一个子串
            i=k;
            j=1;
        }
    }
    if(j>T.length){
        return k;
    }else{
        return 0;
    }
}

若模式串长度为m,主串长度为n,则匹配成功的最好时间复杂度为:O(m)匹配失败的最好时间复杂度为:O(n)

若模式串长度为m,主串长度为n,则直到匹配成功/匹配失败最多需要 ( n − m + 1 ) ∗ m (n-m+1)*m nm+1m次比较。最坏时间复杂度就可表示为:O(nm)


🚢KMP算法

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。

朴素模式匹配算法的缺点:当某些子串与模式串能部分匹配时,主串的扫描指针i会经常回溯,导致时间开销增加。KMP算法的出现就是为了解决主串指针回溯问题。

int Index_KMP(SString T, int next[]){
    int i=1, j=1;
    while(i<=S.length && j<=T.length){
        if(j==0 || S.ch[i] == T.ch[j]){
            ++i;
            ++j;        //继续比较后继字符
        }else{
            j=next[j];      //模式串向右移动
        }
    }
    if(j>T.length){
        return i-T.length;      //匹配成功
    }else{
        return 0;
    }
}

这里只是对KMP算法的 一个简单介绍,并没有过多深入,但是KMP算法是串这一章节中比较重要的一个算法,后面还会涉及到next数组的推算等等,所以将会在以后单独出一篇文章讲解KMP算法。


💻总结


在经过将近一个多月的停更后,本专栏的文章再次开始更新,之前因为个人原因停更,感谢各位小伙伴们的支持与等待,同样,在新文章更新的同时,我也会不断提高自己的创作水平,争取自己的文章能给小伙伴们带来更多的帮助。

本篇文章比较全面的讲解了数据结构中串这一节的详细内容,因其与线性表类似的结构,所以有些点并没有重复提起,但是关于串的知识点,小伙伴们要多花时间去细读文章,串是编程中处理字符串比较重要的数据结构。一如既往希望我的文章能给各位小伙伴们带来帮助,数据结构与算法专栏也在持续更细中!!!


🎨觉得不错的话记得点赞收藏呀!!🎨

😀别忘了给我关注~~😀

  • 39
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 33
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小田是个程序员

各位看官,觉得不错就多支持哟!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值