当前位置: 首页 > article >正文

【数据结构】【线性表】特殊的线性表-字符串

目录

字符串的基本概念        

字符串的三要素

字符串的基本概念

串的编码

串的实现及基本运算

顺序串的实现

串的静态数组实现

串的动态数组的实现

顺序存储的四种方案

链式串的实现

基本运算

方案三

方案一


字符串的基本概念        

        数据结构千千万,但距离我们最近的还是字符串。在这里我们定义:有限个字符组成的序列为字符串。在大多数人看来,字符串仅仅是一个char类型的数组,但在作者看来,数组也是一种特殊的线性表。回想顺序表、顺序栈、顺序队列这些都和数组有着千丝万缕的联系。但在我们这里要讲的字符串,它的格式不仅仅是数组格式,数组格式只是串格式的众多种类中的一种。

        我们回顾数据结构的三要素:逻辑结构、物理结构和基本运算。线性表是指元素之间的逻辑结构为线性的一系列的数据结构,其特点是一对一。类似的,字符串各个元素之间的逻辑结构也可以视为线性,因此字符串是一种特殊的线性表。

字符串的三要素

  1. 逻辑结构:线性结构
  2. 物理结构:顺序存储/链式存储
  3. 基本运算
    1. StrCopy(&T,S)复制串,将串S复制到串T;
    2. StrEmpty(S)串判空,判断串S是否为空串;
    3. StrLength(S)求串长,求串S的长度;
    4. ClearStr(S)清空串,清空串S,使其成为空串;
    5. DestroyStr(&S)销毁串,清空串并释放其内存空间;
    6. ConcatStr(&T,S1,S2)串连两个串,在S1串后面链接上S2成为一个串T;
    7. SubString(&Sub,S,pos,len)求子串,求串S以pos为起点,长度为len的子串;
    8. StrCompare(S,T)比较串,比较串S与T,S>T则返回值大于0;S=T则返回值等于0;S<T则返回值小于0;
    9. Index(S,T)定位子串,如果主串S中存在与T相同的子串,则返回主串第一次出现子串的第一个位置;

上述基本操作涉及到一些串的相关名词,在这里作一下定义说明,没有疑问的可跳过。

字符串的基本概念

字符串:有限个字符组成的序列

  1. 串长:有限字符的个数n
  2. 空串:有限字符个数为0
  3. 空格串:串内所有字符均为空格;例如:“   ”
  4. 主串和子串:这是一个相对概念,一般来说,把一个完整的字符串当成主串,而子串是从主串中任意提出来的连续字符组成的序列
    例如:

    a="qwer1314";
    b="qwer"
    c="1314",
    d="wer1",
    在这里b c d均是a的子串

        注意空串和空格串,空串里面没有一个字符,而空格串是包含了字符,只不过这个字符是空格。

串的编码

  1. 英文字符:ASCII码等。
  2. 中英文字符:UTF8、UTF16、GB2312等。

        我们知道,计算机的存储是二进制的,也就是说无论什么字符,其存储的数据都是二进制,那么如何将二进制数与符号相对应,这就是我们的编码。编码是一种规则,一个二进制数通过这个规则对应表示一个字符。抽象理解成一个y=f(x)的函数,自变量x为二进制数,编码为映射规则f,因变量为字符集y。满足函数一一对应的性质。编码有多种多样的,例如BCD码,格雷码等。

        ASCII码是我们最常用的一种编码,其存储空间一般为1B。它包含了我们常见的大部分英文字符,例如26个大小写英文字母,数字,空格等。其具体的ASCII表在网上众多,我就不一一赘述了。

        UTF8、UTF16、GB2312都是包含了中英文字符的编码,在需要使用中文的体系中常用,但需要注意的是使用这几种的人数较多较杂,不像英文字符大部分人都用ASCII,这几种也很多人使用,因此我们在打开其他人的文件时很容易出现乱码,其主要原因就是你打开打编码方式与他写的编码方式不一致。因此当遇到乱码,应当及时更正打开文件的编码方式。

串的实现及基本运算

顺序串的实现

        顺序串即存储结构为连续顺序的字符串,和顺序表类似,其构建方法一般可以分为静态数组和动态数组两种。

串的静态数组实现

#define MaxSize 100;//定义最大分配空间为100个
typedef struct{
    char str[MaxSize];//定义静态字符数组
    int length;//定义串的实际长度
}SString;

         静态数组在分配得到时候固定了大小空间

串的动态数组的实现

typedef struct{
    char *str;
    int length;
}HString;

        动态数组只分配了数组的基址,大小可自己拓展。在应用过程中拓展需要用到malloc函数,同时销毁元素时也要手动free空间。

顺序存储的四种方案

        方案1是最常见的格式,也是一般字符串的默认格式,不包含表长元素,以'\0'作为结尾符号;

        方案2是我们默认使用的格式,前面是串的各个字符元素,最后存放表长元素;

        方案3是舍弃数组的第一个元素,使数组下标与字符的位序对其,最后存放表长元素;

        方案4不单独设置表长元素,将串的字符数组的第一个元素存放表长数据, 同时也让数组下标与字符位序对齐;但表长元素的数据类型是int,字符数组的数据类型是char,使用中有很多需要注意,因此实用性不是很好。

        四个方案相较而言方案3更具有优势,但方案1的普遍性更强。

链式串的实现

        链式串的物理结构类似于单链表,每个结点存放一组数据,并指向下一个结点,结构示意图:

        其中S是头结点,从1开始才是串的实际字符数据;当然采用无头结点的串也是可以的,只需要注意第一个结点特殊处理就行。

typedef struct StringNode{
    char str;//每个结点存储一个字符
    struct StringNode *next;//指向下一个结点
}StringNode,*String;

        一般而言我们会采用一个结点储存一个字符,但有些特殊情况下需要一个结点储存固定长度的字符,同时也节省空间。

typedef struct StringNode{
    char str[4];//每个结点存储4个字符
    struct StringNode *next;//指向下一个结点
}StringNode,*String;

基本运算

        在讲基本操作之前,我们先确定一下其串的格式,物理存储格式决定这基本运算的操作思路、步骤与顺序。从编程的逻辑性和简易性而言,方案三是最合适的。

        但又考虑到普遍的字符串存储格式是方案一:

         因此我们选择在方案三的基础上完成思路和源码的讲解,同时展示完整的方案一源码,在源码中简单讲解。

方案三

        方案三字符串结构体创建

#define MaxSize 100;//定义最大分配空间为100个
typedef struct{
    char str[MaxSize];//定义静态字符数组
    int length;//定义串的实际长度
}SString;
/*初始化串*/
void InitString(SString &S){
    for(int i=0;i<MaxSize;i++)//将所有值设定为字符串终止符,也可以是别的没歧义的内容
        S.str[i]='\0';

    S.length=0;//初始化字符串长度为0
}

         复制串,将串S复制得到串T

void StrCopy(SString &T,SString S){
    T.length=S.length;
    for(int i=1;i<=T.length;i++){
        T.str[i]=S.str[i];
    }
}

        串判空,判断串是否为空串,是返回true,不是返回false

bool StrEmpty(SString S){
    if(S.length==0)//字符串长度为0则为空字符串
        return true;
    else if(S.length>0)
        return false;
}

         求串长,在这个格式下有点多此一举,但也还是说明一下吧

int StrLength(SString S){
    return S.length;
}

        清空串,使其成为空串,但其实在这里没办法真的成为空,毕竟空间就在那,一般操作方式是给每个字符空间赋值为特殊值,例如0或者'\0',然后修改表长。对于char数据类型建议赋值为'\0';对于其他int或者float建议赋值为0

void ClearString(SString &S){
    for(int i=0;i<=S.length;i++)
        S.str[i]='\0';
    S.length=0;
}

         销毁串,因为这个结构采用的是静态数组,静态数组不需要手动释放空间,内存会在超出变量作用域时自动释放空间。

        连接串,串连两个串,在S1串后面连接上S2成为一个串T;

bool ConcatString(SString &T,SString S1,SSrting S2){
    T.length=S1.length+S2.length;//拼接后的长度

    if(T.length+1>MaxSize)//判断长度是否超限
        return false;//长度超限,拼接失败

    for(int i=1;i<=S1.length;i++)//将S1的数据复制到T
        T.str[i]=S1.str[i];

    for(int i=1;i<=S2.length;i++)//将S2的数据复制到S1后面
        T.str[S1.length+i]=S2.str[i];

    return true;//拼接成功
}

        求子串,求主串S第pos位开始的长len的子串

bool SubString(SString &Sub,SString S,int pos,int len){
    if(pos+len-1>S.length)//判断子串是否越界
        return false;//子串越界,获取子串失败

    Sub.length=len;//子串长度为len

    for(int i=1;i<len+1;i++)//读取子串并复制给Sub
        Sub.str[i]=S.str[i+pos-1];

    Sub.str[i]='\0';//给子串添加终止符
 
    return true;//获取子串成功
}

        StrCompare(S,T)比较串,比较串S与T,S>T则返回值大于0;S=T则返回值等于0;S<T则返回值小于0。

        有人就说了字符串怎么能比较呢?这也是作者刚开始疑惑的问题,字符串比什么,除了能比一不一样还能干嘛?所以在比较字符串之前一定要有比较的标准,制定比较标准时需要注意以下三个要点

  • 要不要考虑字符的大小写
  • 字符串的长度
  • 对于特殊字符和不同编码如何处理

 下面我们先制定字符串比较的规则:

  1. 比较对字符大小写敏感,直接按顺序比较字符在ASCLL的码值,先出现更大字符的串更大
  2. 考虑字符串长度,长串前缀与短串字符一致时,长串更大
  3. 不考虑特殊字符,默认均为ASCII码
int StrCompare(SString S,SString T){
    //遍历查找两个字符串每个位置的字符比较情况
    for(int i=1;i<=S.length && i<=T.length;i++){
        if(S.str[i]!=T.str[i])//字符不相同则相减并返回
            return S.str[i]-T.str.[i];
    }

    return S.length-T.length;//
}

定位子串, 如果主串S中存在与T相同的子串,则返回主串第一次出现子串的第一个位置。

int IndexStr(SString S,SString T){
    int m=S.length;
    int n=t.length;
    int flag;
    SString Sub;
    for(int i=1;i<=n-m+1;i++){
        /*取出字符串S中长度为n的子串*/
        if(i+n-1>S.length)//判断子串是否越界
            return 0;//子串越界,获取子串失败

        Sub.length=n;//子串长度为n

        for(int j=1;j<=n;j++)//读取子串并复制给Sub
            Sub.str[j]=S.str[j+i-1];
        /*比较T与S的子串Sub*/
        for(int j=1;j<=S.length && j<=T.length;j++){
            if(S.str[j]!=Sub.str[j])
                flag=T.str[j]-Sub.str[j];
        }

        flag=S.length-T.length;
        /*判断两串是否相等*/
        if(flag==0)
            return i;
    }

    return 0;//没有到与T相等的子串
}

 上述代码中有求子串和比较子串的操作,我们采用源码表示,但也可以直接用函数:

int IndexStr(SString S,SString T){
    int m=S.length;
    int n=t.length;
    bool flag;
    SString Sub;
    for(int i=1;i<=n-m+1;i++){
        /*取出字符串S中长度为n的子串*/
        flag=SubString(Sub,S,i,n);
        if(flag==false)
            return 0;//子串违法
        /*判断两串是否相等*/
        if(StrCompare(T,Sub)==0)
            return i;
    }

    return 0;//没有到与T相等的子串
}
方案一

方案一是最常见的字符串类型例如:

char str[]="qwer1314";

 这种方式不需要结构体,同时也是我们在大多数场合输入字符串的默认格式,因此在这里我们再次进行代码讲解;

/*将字符串S复制给T*/
void StrCopy(char *S,char *T){
    int i=0;
    while(S[i]!='\0'){
        T[i]=S[i];
        i++;
    }
    
    T[i]='\0';
}
/*判定字符串S是否为空*/
bool StrEmpty(char *S){
    if(S[0]=='\0')
        return true;
    else
        return false;
}
/*求字符串S的长度*/
int StrLength(char *S){
    int len=0;
    while(S[len]!='\0'){
        len++;
    }
    return len;
}
/*清空字符串*/
void ClearString(char *S){
    int i=0;
    while(S[i]!='\0'){
        S[i]='\0';
        i++;
    }
}
/*串联两个字符串*/
char *ConcatStr(const char *str1, const char *str2) {
    // 检查输入是否为NULL
    if (str1 == NULL || str2 == NULL) {
        return NULL;
    }

    // 计算新字符串的长度
    int len1 = StrLength(str1);
    
    int len2 = StrLength(str2);

    int newLen = len1 + len2 + 1; 

    // 分配内存
    char *result = (char *)malloc(newLen * sizeof(char));
    if (result == NULL) {
        // 内存分配失败
        return NULL;
    }

    // 复制第一个字符串到结果中
    int i = 0;
    while (i < len1) {
        result[i] = str1[i];
        i++;
    }

    // 连接第二个字符串到结果中
    int j = 0;
    while (j < len2) {
        result[i + j] = str2[j];
        j++;
    }

    // 添加终止符
    result[i + j] = '\0';

    return result;
}
/*求子串*/
char *SubString(char *str, int pos, int len) {
    int length = StrLength(str);
    if (pos + len > length || pos < 0 || len < 0) {
        return NULL; // 检查边界条件
    }
    // 分配内存
    char *result = (char *)malloc((len + 1) * sizeof(char));
    if (result == NULL) {
        return NULL; // 检查内存分配是否成功
    }
    for (int i = 0; i < len; i++) {
        result[i] = str[pos + i];
    }
    result[len] = '\0'; // 添加字符串结束符

    return result;
}
/*比较字符串*/
int Strcompare(char *str1,char *str2){
    int len1=StrLength(str1),len2=StrLebgth(str2);
    for(int i=0;i<len1 && i<len2;i++){
        if(str1[i]!=str2[i])
            return str1[i]-str2[i];
    }

    return len1-len2;
}
/*定位字符串*/
int Index(char *str1,char *str2){
    int m=StrLength(str1);
    int n=StrLength(str2);
    bool flag;
    char *Sub;
    for(int i=1;i<=n-m+1;i++){
        /*取出字符串S中长度为n的子串*/
        Sub=SubString(S,i,n);
        if(flag==NULL)
            return 0;//子串违法
        /*判断两串是否相等*/
        if(StrCompare(S,Sub)==0)
            return i;
    }

    return 0;//没有到与T相等的子串
}

常见字符串操作:

将字符串转换成int

//字符串转化为int
int string_int(char *str) {
    if (str == NULL || *str == '\0') {
        // 输入为空或空字符串
        return 0;
    }

    int num = 0;
    int i = 0;
    int flag = 1;

    // 检查负号
    if (str[i] == '-') {
        flag = -1;
        i++;
    }

    // 转换数字部分
    while (str[i] >= '0' && str[i] <= '9') {
        int digit = str[i] - '0';

        // 检查是否会发生溢出
        if (num > (INT_MAX - digit) / 10) {
            // 发生溢出,返回0或其他错误值
            return 0;
        }

        num = num * 10 + digit;
        i++;
    }

    return flag * num;
}

将int转化为字符串

char *int_string(int num) {
    // 假设整数最多有11位(包括负号)
    char *str = (char *)malloc(12 * sizeof(char)); // 分配内存用于存储转换后的字符串
    if (str == NULL) {
        return NULL; // 如果内存分配失败,返回NULL
    }

    int i = 0; // 用于记录当前字符的位置
    int isNegative = 0; // 标记是否为负数

    if (num < 0) {
        isNegative = 1; // 如果是负数,设置标记并取绝对值
        num = -num;
    }

    // 将数字转换为字符串
    do {
        str[i++] = (num % 10) + '0'; // 将最低位的数字转换为字符并存储
        num /= 10; // 去掉已经处理的最低位
    } while (num > 0);

    if (isNegative) {
        str[i++] = '-'; // 如果是负数,添加负号
    }

    // 反转字符串
    for (int j = 0; j < i / 2; j++) { // 注意这里需要声明j的类型为int
        char temp = str[j];
        str[j] = str[i - j - 1];
        str[i - j - 1] = temp;
    }

    // 添加字符串终止符
    str[i] = '\0';

    return str; // 返回转换后的字符串
}

将字符串转换成float

// 将字符串转换为浮点数的函数
float string_float(char *str) {
    // 如果输入为空或空字符串,返回0.0f
    if (str == NULL || *str == '\0') {
        return 0.0f;
    }

    // 初始化变量
    float num = 0.0f; // 最终结果
    int i = 0; // 当前字符索引
    int flag = 1; // 符号标志,1表示正数,-1表示负数
    int fractional_part = 0; // 是否处理小数部分的标志
    float fractional_divisor = 1.0f; // 小数部分的除数

    // 检查负号
    if (str[i] == '-') {
        flag = -1;
        i++;
    }

    // 转换数字部分和小数部分
    while (str[i] != '\0') {
        if (str[i] >= '0' && str[i] <= '9') {
            int digit = str[i] - '0'; // 将字符转换为数字
            if (fractional_part) {
                // 处理小数部分
                fractional_divisor *= 10.0f;
                num += digit / fractional_divisor;
            } else {
                // 处理整数部分
                if (num > (FLT_MAX - digit) / 10.0f) {
                    // 发生溢出,返回0或其他错误值
                    return 0.0f;
                }
                num = num * 10.0f + digit;
            }
        } else if (str[i] == '.' && !fractional_part) {
            // 遇到小数点,开始处理小数部分
            fractional_part = 1;
        } else {
            // 非法字符,直接返回当前结果
            break;
        }
        i++;
    }

    // 返回最终结果,考虑符号
    return flag * num;
}

将float转换为字符串

// 将浮点数转换为字符串的函数
char *float_string(float num) {
    // 假设浮点数最多有38位(包括负号、小数点和指数)
    char *str = (char *)malloc(40 * sizeof(char)); // 分配内存用于存储转换后的字符串
    if (str == NULL) {
        return NULL; // 如果内存分配失败,返回NULL
    }

    int i = 0; // 用于记录当前字符的位置
    int isNegative = 0; // 标记是否为负数

    if (num < 0) {
        isNegative = 1; // 如果是负数,设置标记并取绝对值
        num = -num;
    }

    // 处理整数部分
    int integerPart = (int)num; // 获取整数部分
    do {
        str[i++] = (integerPart % 10) + '0'; // 将整数部分逐位转换为字符并存储
        integerPart /= 10; // 去掉已经处理的最低位
    } while (integerPart > 0);

    if (isNegative) {
        str[i++] = '-'; // 如果是负数,添加负号
    }

    // 反转整数部分,使其按正确顺序排列
    for (int j = 0; j < i / 2; j++) {
        char temp = str[j];
        str[j] = str[i - j - 1];
        str[i - j - 1] = temp;
    }

    // 添加小数点
    str[i++] = '.';

    // 处理小数部分
    float fractionalPart = num - (int)num; // 获取小数部分
    int precision = 6; // 设置精度为6位小数
    for (int k = 0; k < precision; k++) {
        fractionalPart *= 10; // 将小数部分放大10倍
        int digit = (int)fractionalPart; // 获取当前最高位的小数
        str[i++] = digit + '0'; // 将小数转换为字符并存储
        fractionalPart -= digit; // 去掉已经处理的最高位小数
    }

    // 添加字符串终止符
    str[i] = '\0';

    return str; // 返回转换后的字符串
}


http://www.kler.cn/a/421270.html

相关文章:

  • 重生之我在异世界学编程之C语言:选择结构与循环结构篇
  • 推荐学习笔记:矩阵补充和矩阵分解
  • 查看 tomcat信息 jconsole.exe
  • Ubuntu22.04上kdump和crash的使用
  • 【ArkTS】使用AVRecorder录制音频 --内附录音机开发详细代码
  • Qt—QLineEdit 使用总结
  • Android中使用NSD扫描,实现局域网内设备IP的发现
  • 第1章:CSS简介 --[CSS零基础入门]
  • Scala 的match case 匹配元组
  • zerotier实现内网穿透
  • M31系列LoRa分布式IO模块功能简介
  • 黑马2024AI+JavaWeb开发入门Day04-SpringBootWeb入门-HTTP协议-分层解耦-IOCDI飞书作业
  • 【微服务】SpringBoot 对接飞书多维表格事件回调监听流程详解
  • 基于Linux的逻辑订阅发布搭建
  • 多线程运行时,JVM(Java虚拟机)的内存模型
  • Runway 技术浅析(七):视频技术中的运动跟踪
  • [TLS] 使用 OpenSSL 命令行测试 TLS13 0-RTT
  • 关于机器学习领域的预测算法/模型基础入门
  • 【论文笔记】Frequency Domain Model Augmentation for Adversarial Attack
  • BioDeepAV:一个多模态基准数据集,包含超过1600个深度伪造视频,用于评估深度伪造检测器在面对未知生成器时的性能。
  • 【ETCD】ETCD用户密码认证
  • HTML5技术贴:现代网页开发的革命
  • 迁移学习!超高创新!GASF-AlexNet-MSA,基于格拉姆角场和AlexNet结合多头注意力机制的故障识别程序
  • 数据结构 - 排序(四):排序算法总结与对比
  • KVCKVO
  • uniapp:封装商品列表为组件并使用