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

【数据结构】串——(一)

目录

    • 串的概念
    • 串的存储表示及实现
      • 定长顺序存储表示 SString(静态存储)
      • 堆分配存储表示 HString(动态存储)
      • 块链存储表示 LString(链表方式)
      • 串赋值(StrAssign)操作
      • 串复制(StrCopy)操作
      • 判空(StrEmpty)操作
      • 求串长(StrLength)操作
      • 串比较(StrCompare)操作
      • 清空串(ClearString)操作
      • 串连接(Concat)操作
      • 求子串(SubString)操作
      • 定位操作(Index / StrIndex)操作
      • 替换操作(Replace)操作
      • 插入(StrInsert)操作
      • 删除(StrDelete)操作
      • 销毁(DestroyString)操作
      • 操作集合

串的概念

串的基本概念

  • 定义
    串(String)是由 零个或多个字符 组成的 有限序列
    • 一般记作:S = "a1 a2 a3 … an"
    • 其中:
      • S 是串的名字
      • a1、a2、…、an 是串的各个字符
      • n 表示串的长度
  • 注意
    • 串是 特殊的线性表:数据元素限定为“字符”。
    • 和普通线性表不同,串主要是用于 处理字符数据,常见操作和应用方向会偏向文本。

串的相关概念

  1. 空串:长度为 0 的串,记作 ""
  2. 空格串:由一个或多个空格字符组成的串,⚠️ 和空串不同。长度为空格的数量。
  3. 子串:串中任意连续的若干个字符组成的子序列。
    • 例:"abcdef" 的子串有 "abc""de" 等。
  4. 主串:包含子串的串。
    • "abcdef" 里,"abc" 是子串,而 "abcdef" 是主串。
  5. 字符位置:字符在串中的序号,一般 从 1 开始(和数组不同)。

串与线性表的区别

  • 相同点:串和线性表一样,都是 线性存储结构,有序排列。
  • 不同点
    1. 数据对象范围不同
      • 线性表元素可以是任意数据对象。
      • 串的元素是 字符
    2. 基本操作不同
      • 线性表操作侧重于 插入、删除
      • 串操作侧重于 查找、匹配、拼接、截取

线性表 → 存什么都行,比如学生、商品、成绩

→ 只能存字符,主要用于处理“文本”类问题,比如:搜索、替换、匹配(比如你常用的字符串操作函数 strlenstrcmpstrcatstrstr 等其实就是“串的操作”)。

串的常见基本操作

  1. 串赋值(StrAssign)

    • 将一个常量字符串赋给串变量。

    • 例:StrAssign(S, "hello")

  2. 串复制(StrCopy)

    • 把一个串复制到另一个串。

    • 例:StrCopy(T, S)

  3. 判空(StrEmpty)

    • 判断一个串是否为空串(长度 = 0)。

    • 例:StrEmpty(S)true or false

  4. 求串长(StrLength)

    • 返回串的字符个数。

    • 例:StrLength("abc") = 3

  5. 串比较(StrCompare)

    • 按字典顺序比较两个串的大小。

    • 例:

      • "abc" < "abd"
      • "abc" = "abc"
  6. 清空串(ClearString)

    • 把串置为空串。
  7. 串连接(Concat)

    • 生成一个新串,其值是两个串连接而成。

    • 例:Concat("hello", "world") = "helloworld"

  8. 求子串(SubString)

    • 截取主串中从第 i 个字符开始、长度为 len 的子串。

    • 例:SubString("abcdef", 2, 3) = "bcd"

  9. 定位操作(Index / StrIndex)

    • 在主串中查找某个子串第一次出现的位置。

    • 例:

      • Index("hello world", "world") = 7
      • 若不存在则返回 0 或 -1(视实现而定)。
  10. 替换操作(Replace)

    • 用新的子串替换主串中所有出现的某个子串。

    • 例:Replace("abcabc", "ab", "xy") = "xycxyc"

  11. 插入(StrInsert)

    • 在主串的第 i 个字符前插入一个子串。

    • 例:Insert("abc", 2, "XYZ") = "aXYZbc"

  12. 删除(StrDelete)

    • 删除主串中第 i 个字符开始的若干字符。
      • 例:Delete("abcdef", 2, 3) = "aef"
  13. 销毁(DestroyString)

    • 释放串所占用的存储空间,使之成为一个无效的串变量。

串的存储表示及实现

定长顺序存储表示 SString(静态存储)

定义:用一个 定长数组 存放串中的字符。

特点

  • 数组大小预先固定(比如 MAXSIZE=255)。
  • 实际串长 ≤ MAXSIZE

**结构体定义:

#define MAXSIZE 255
typedef struct {char ch[MAXSIZE]; // 存放串的字符int length;       // 串的实际长度
} SString;

优点:简单,容易实现。

缺点:空间可能浪费,串长受数组上限限制。

堆分配存储表示 HString(动态存储)

定义:用一段 动态分配的连续存储区 存放串的字符。

特点

  • 串长不受预定义常量限制,只要内存够就行。
  • 适合变长串的存储。

结构体定义

typedef struct {char *ch;   // 指向动态分配的字符数组int length; // 串的长度
} HString;

例子

  • 当串长度变化时,可以通过 malloc / realloc 动态调整。

优点:灵活,适合实际工程。

缺点:需要频繁申请/释放内存,管理成本较高。

块链存储表示 LString(链表方式)

定义:把串的字符分散存放在若干个链表节点中,每个结点可以存放多个字符。

结构体定义

#define BLOCK_SIZE 4 // 每个结点存4个字符(示例)typedef struct Block {char ch[BLOCK_SIZE];struct Block *next;
} Block;typedef struct {Block *head, *tail; // 串的头结点和尾结点int length;         // 串的总长度
} LString;

特点

  • 插入、删除效率高(尤其是大串处理)。
  • 适合超长串(比如几 MB 的大文本)。

缺点:存储密度低(指针要额外占空间),查找定位某个字符时效率较低(必须顺链)。

串赋值(StrAssign)操作

SString 的 StrAssign 的逻辑

  1. 判断源串长度是否超过 SString 最大长度 MAXSIZE
  2. 遍历源串,将每个字符逐个复制到 S.ch 中
  3. 设置 S.length 为实际字符数

⚠️ 注意:SString 是定长顺序存储,所以不能超过数组容量。

// 串赋值
int StrAssign(SString *S, const char *chars) {int i = 0;while (chars[i] != '\0') {   // 遍历源字符串if (i >= MAXSIZE)        // 超过容量报错return 0;            // 赋值失败S->ch[i] = chars[i];     // 复制字符i++;}S->length = i;               // 设置长度return 1;                    // 赋值成功
}

StrAssign 是 SString 的管理类操作(和复制、清空类似),主要用于初始化或重新赋值。

如果要赋值的字符串长度超过 MAXSIZE,需要截断或报错,这就是 定长顺序存储的局限

HString 的 StrAssign 的逻辑

  1. 如果 H->ch 不为空,先 free(H->ch),释放原有空间
  2. 计算源字符串长度 len
  3. 分配新空间:H->ch = (char*)malloc(sizeof(char) * len)
  4. 将源字符串字符逐一复制到 H->ch
  5. 设置 H->length = len
  6. 返回成功标志

⚠️ 注意:

  • HString 是动态分配,内存需要手动释放
  • 如果 malloc 失败,要返回失败
int StrAssign(HString *H, const char *chars){int len = strlen(chars);if(H->ch) free(H->ch); // 释放原有空间H->ch = (char*) malloc(sizeof(char) * (len + 1)); // +1 给 '\0'if(!H->ch) return 0; // 分配失败strcpy(H->ch, chars); // 复制字符串H->length = len;return 1;
}

HString 与 SString 的区别:

  1. 动态分配:长度可变,可释放空间
  2. 赋值操作需 malloc,而 SString 直接写入数组

后续操作(连接、插入、删除等)也都需要注意 空间是否足够

LString 的 StrAssign 的逻辑

  1. 如果 L->head 不为空,先释放原链表
  2. 初始化头结点:L->head = NULL 或创建一个空头结点
  3. 遍历源字符串 chars:
    • 创建新结点 node
    • node->data = chars[i]
    • 将 node 插入链表尾部
  4. 更新 L->length
  5. 返回成功标志

⚠️ 注意:

  • 链表操作中,插入到尾部通常需要维护一个 尾指针,避免每次都从头遍历
  • 分配结点失败,需要返回失败
// 释放链表
void FreeLString(LString *L){LNode *p = L->head;while(p){LNode *tmp = p;p = p->next;free(tmp);}L->head = NULL;L->length = 0;
}// 串赋值 StrAssign
int StrAssign(LString *L, const char *chars){FreeLString(L); // 释放原链表L->head = NULL;LNode *tail = NULL;int i=0;while(chars[i]!='\0'){LNode *node = (LNode*) malloc(sizeof(LNode));if(!node) return 0; // 分配失败node->data = chars[i];node->next = NULL;if(L->head == NULL){L->head = node;tail = node;} else {tail->next = node;tail = node;}i++;}L->length = i;return 1;
}

LString 优点:长度不固定,插入删除效率高

LString 缺点:访问单个字符需要遍历,随机访问效率低

与 HString 对比:

  • HString 内存连续,随机访问快
  • LString 内存链表,动态灵活

串复制(StrCopy)操作

SString 的 StrCopy 的逻辑

  1. 遍历源串 S.ch,将每个字符复制到目标串 T.ch。
  2. 设置 T.length = S.length。

⚠️ 注意:SString 是定长顺序存储,目标串容量不能小于源串长度

// 串复制
int StrCopy(SString *T, const SString *S) {if(S->length > MAXSIZE)return 0; // 复制失败,超出容量for(int i = 0; i < S->length; i++) {T->ch[i] = S->ch[i];}T->length = S->length;return 1; // 复制成功
}

StrCopy 属于 管理类操作(类似赋值、清空)。

和 StrAssign 不同的是:

  • StrAssign 是从 字符常量赋值
  • StrCopy 是从 另一个 SString 赋值

注意容量限制:SString 的长度不能超过 MAXSIZE

HString 的 StrCopy 的逻辑

  1. 如果目标串 T->ch 不为空,先 free(T->ch)
  2. 获取源串长度 len = S->length
  3. 分配新空间:T->ch = malloc(len + 1)
  4. 将源串 S->ch 复制到目标串 T->ch
  5. 设置 T->length = S->length
  6. 返回成功标志

⚠️ 注意:

  • 必须处理目标串原有空间释放,否则会内存泄漏
  • 动态分配失败需要返回失败
// 串复制 StrCopy
int StrCopy(HString *T, const HString *S){if(T->ch) free(T->ch); // 释放原有空间T->ch = (char*) malloc(sizeof(char) * (S->length + 1));if(!T->ch) return 0; // 分配失败strcpy(T->ch, S->ch);T->length = S->length;return 1;
}

与 SString 对比

  • SString 是静态数组,复制直接逐个赋值
  • HString 需要 动态分配内存,保证长度可变

注意内存管理

  • 复制前释放目标串原有空间
  • 使用完后要 free() 避免内存泄漏

LString 的 StrCopy 的逻辑

  1. 释放目标串 T 的旧链表

  2. 若源串 S 为空串 → 直接返回空串

  3. 遍历源串 S:

    • 为每个字符分配新结点

    • 将字符复制到结点

    • 按顺序接到目标串链表中

  4. 更新 T->length

  5. 返回成功标志

// 释放链表
void FreeLString(LString *L){LNode *p = L->head;while(p){LNode *tmp = p;p = p->next;free(tmp);}L->head = NULL;L->length = 0;
}// 串复制 StrCopy
int StrCopy(LString *T, const LString *S){FreeLString(T); // 清空目标串T->head = NULL;if(S->length == 0) { // 源串为空T->length = 0;return 1;}LNode *p = S->head, *tail = NULL;while(p){LNode *node = (LNode*) malloc(sizeof(LNode));if(!node) return 0; // 分配失败node->data = p->data;node->next = NULL;if(T->head == NULL){T->head = node;tail = node;} else {tail->next = node;tail = node;}p = p->next;}T->length = S->length;return 1;
}
  • SString / HString 的复制对比:
    • SString:直接数组拷贝(静态存储,空间固定)
    • HStringmalloc 一次性分配整段空间再 strcpy
    • LString:逐个结点 malloc 并复制字符

👉 所以 LString 的复制效率最低,但灵活性最高(支持无限长串,不受数组限制)。

判空(StrEmpty)操作

SString 的 StrEmpty 的逻辑

  1. 检查 S.length 是否为 0
  2. 如果是 0 → 返回真
  3. 否则 → 返回假

⚠️ 注意:S.ch 中的内容无所谓,只要 length=0 就是空串。

// 判空操作
int StrEmpty(const SString *S) {return (S->length == 0);
}

判空操作非常简单,属于 管理类操作

在其他串操作(如插入、删除、连接)前,通常先判空,以避免越界。

空串:length = 0

非空串:length > 0

HString 的 StrEmpty 的逻辑

  1. 检查 H->length 是否为 0
  2. 可选:也可检查 H->ch 是否为 NULL
  3. 返回布尔值
int StrEmpty(const HString *H){return (H->length == 0) ? 1 : 0;
}

HString 判空 直接判断 length 是否为 0,效率非常高

SString 判空类似,只是 HString 的长度是动态维护的

释放内存后也算空串,length = 0

LString 的 StrEmpty 的逻辑

  1. 检查 L->length 是否为 0
  2. 或者检查 L->head 是否为 NULL
  3. 返回布尔值

链式存储的串,空串通常 head = NULL 且 length = 0,两者保持一致

// 判空 StrEmpty
int StrEmpty(const LString *L){return (L->length == 0) ? 1 : 0;
}

判断依据:链表长度 length 或头指针 head

特点:效率高(O(1)),不需要遍历链表

SString / HString 判空逻辑相似,只是底层存储不同

求串长(StrLength)操作

SString 的 StrLength 的逻辑

  1. 读取 S.length
  2. 返回该值

⚠️ 注意:SString 已经存储了长度信息,所以 不需要遍历数组

  • 这是 SString 的优势之一:求长度操作 O(1)
// 求串长操作
int StrLength(const SString *S) {return S->length;
}

SString 来说,求长度操作非常高效,只需返回 length 成员。

HString 或 LString,如果没有缓存长度,求长度可能需要遍历整个字符数组或链表,复杂度 O(n)。

这个操作通常用于:循环、判空、插入、删除、截取等其他操作前的判断。

HString 的 StrLength 的逻辑

  1. 直接访问 H->length
  2. 返回值

HString 在赋值、插入、删除等操作中会维护 length,所以求串长是 O(1) 操作

// 求串长 StrLength
int StrLength(const HString *H){return H->length;
}

HString 的长度 length动态维护的字段,不需要遍历数组

SString 对比:

  • SString 静态数组也可以维护 length,同样 O(1)
  • 如果不维护 length,则需要遍历数组统计字符个数(O(n))

求串长操作非常高效,是其他操作(插入、删除、比较)的基础

LString 的 StrLength 的逻辑

  1. 若 LString 维护 length,直接返回 L->length
  2. 若没有维护 length,则:
    • 遍历链表,从头结点到尾结点
    • 每遍历一个结点计数加 1
    • 返回计数

通常我们在赋值、插入、删除时维护 length,所以求串长是 O(1) 操作

// 求串长 StrLength
int StrLength(const LString *L){return L->length;
}

维护 length 字段:O(1) 时间求长度

不维护 length:需遍历链表,O(n)

HString / SString 对比:

  • HString:动态数组,length O(1)
  • SString:静态数组,维护 length O(1)
  • LString:链表,维护 length O(1),不维护 O(n)

串比较(StrCompare)操作

SString 的 StrCompare 的逻辑

  1. 从第 1 个字符开始,依次比较 S.ch[i] 和 T.ch[i]
  2. 遇到不同字符:返回它们 ASCII 值差 S.ch[i] - T.ch[i]
  3. 遍历完较短串后:
    • 如果所有字符相等,则 长度短的串小

⚠️ 注意:

  • SString 的下标一般从 0 或 1,教材里常从 1 开始
  • 比较的是 字典序(ASCII 顺序)
// 串比较
int StrCompare(const SString *S, const SString *T) {int i = 0;int minLen = (S->length < T->length) ? S->length : T->length;for(i = 0; i < minLen; i++) {if(S->ch[i] != T->ch[i])return S->ch[i] - T->ch[i];  // ASCII差值}// 前 minLen 个字符相同,则长度短的串小return S->length - T->length;
}

StrCompare 属于 管理类操作,常用于:

  • 字符串排序
  • 查找匹配
  • 字典序判断

SString:复杂度 O(n),n 为较短串长度。

HString / LString:逻辑相同,但 HString 也可以直接遍历数组,LString 需要顺链访问。

HString 的 StrCompare 的逻辑

  1. 从两个串的第一个字符开始逐个比较
  2. 当遇到第一个不同的字符时:
    • 返回 S->ch[i] - T->ch[i]
  3. 若一串提前结束:
    • 返回长度差 S->length - T->length
  4. 若完全相同:
    • 返回 0

⚠️ 注意:

  • HString 是动态数组,可随机访问,比较效率高
  • 可以直接用循环或 strncmp
// 串比较 StrCompare
int StrCompare(const HString *S, const HString *T){int minLen = (S->length < T->length) ? S->length : T->length;for(int i=0; i<minLen; i++){if(S->ch[i] != T->ch[i])return S->ch[i] - T->ch[i];}return S->length - T->length; // 长度不同,较长串大
}

原理:逐字符比较 ASCII 值

效率:O(n),n = 较短串长度

与 SString / LString 对比

  • SString:数组顺序访问,逻辑一致
  • LString:链表,需要遍历结点,访问慢一些

LString 的 StrCompare 的逻辑

  1. 从两个串的头结点开始逐结点比较 data
  2. 当遇到第一个不同字符时:
    • 返回 S->data - T->data
  3. 若一串提前结束:
    • 返回长度差 S->length - T->length
  4. 若完全相同:
    • 返回 0

⚠️ 注意:

  • 链表存储不能随机访问,需要顺序遍历
  • 时间复杂度 O(n),n = 较短串长度
// 串比较 StrCompare
int StrCompare(const LString *S, const LString *T){LNode *p = S->head;LNode *q = T->head;while(p && q){if(p->data != q->data)return p->data - q->data;p = p->next;q = q->next;}return S->length - T->length;
}

链表存储串访问较慢,但灵活性高(不受固定数组长度限制)

HString / SString 对比:

  • SString / HString 可以随机访问数组,效率略高
  • LString 逐结点遍历,效率稍低

时间复杂度 O(n),n = 较短串长度

清空串(ClearString)操作

SString 的 ClearString 的逻辑

  1. 将 S.length 置为 0
  2. 可选:把 S.ch 数组中的字符清空(不是必须)

⚠️ 注意:

  • 清空 ≠ 销毁
    • 清空串:空间保留,可继续使用
    • 销毁串:释放空间,串不可再用
// 清空串
void ClearString(SString *S) {S->length = 0;// 可选:清空数组内容// for(int i=0;i<MAXSIZE;i++)//     S->ch[i] = '\0';
}

ClearString 是 管理类操作

常用于:

  • 重新使用串
  • 避免旧内容干扰后续操作

SString 来说,时间复杂度 O(1)

HString / LString,如果要释放原有动态空间或链表节点,逻辑略有不同

HString 的 ClearString 的逻辑

  1. H->length = 0
  2. 可选:将 H->ch 内存清零(便于调试)
  3. 不释放 H->ch,空间仍可使用

销毁 DestroyString 的区别:

  • ClearString:长度置 0,空间可用
  • DestroyString:释放空间,串变量变无效
// 清空串 ClearString
void ClearString(HString *H){H->length = 0;// 可选:清空内存// if(H->ch) memset(H->ch, 0, sizeof(char)*(H->length+1));
}

ClearString 与 DestroyString 的区别

操作长度内存空间可再次赋值
ClearString0保留可以
DestroyString0释放需重新分配

清空操作 O(1),非常高效

对 HString 的管理操作(判空、长度、清空)都是基于 length 字段

LString 的 ClearString 的逻辑

  1. 遍历链表释放每个结点
  2. L->head = NULL
  3. L->length = 0
  4. 串变量仍然可用,便于重新赋值

销毁 DestroyString 的区别:

  • ClearString:释放结点,变量可用
  • DestroyString:释放结点,变量变无效(通常还要清掉结构体内容)
// 清空串 ClearString
void ClearString(LString *L){LNode *p = L->head;while(p){LNode *tmp = p;p = p->next;free(tmp);}L->head = NULL;L->length = 0;
}

ClearString 与 DestroyString 的区别

操作长度内存空间串变量可用
ClearString0已释放结点可以
DestroyString0已释放结点变无效

清空操作的时间复杂度 O(n),n = 结点数

对链式串管理非常重要,避免内存泄漏

串连接(Concat)操作

SString 的 Concat 的逻辑

  1. 计算 S1.length + S2.length
  2. 如果总长度 ≤ MAXSIZE → 直接复制
    • 先把 S1.ch 复制到 T.ch
    • 再把 S2.ch 追加到 T.ch 后面
    • 设置 T.length = S1.length + S2.length
  3. 如果总长度 > MAXSIZE → 截断(或报错)

⚠️ 注意:SString 是 定长顺序存储,不能超过 MAXSIZE

// 串连接
int Concat(SString *T, const SString *S1, const SString *S2) {if(S1->length + S2->length > MAXSIZE)return 0; // 超出容量,连接失败int i;for(i=0;i<S1->length;i++)T->ch[i] = S1->ch[i];for(int j=0;j<S2->length;j++,i++)T->ch[i] = S2->ch[j];T->length = S1->length + S2->length;return 1; // 连接成功
}

Concat 属于 处理类操作(与求子串、查找、插入、删除同类)

SString,时间复杂度 O(n + m),n、m 分别为 S1 和 S2 长度

HString / LString,可利用动态分配或链表拼接,更灵活

HString 的 Concat 的逻辑

  1. 分配长度为 S->length + T->length + 1 的新空间
  2. 将 S->ch 的内容复制到新空间
  3. 将 T->ch 的内容接到 S 后面
  4. 更新 H->length
  5. H->ch 指向新空间

⚠️ 注意:

  • HString 是动态数组,连接需要新分配内存
  • 原来的 H->ch 如果存在,要先释放
// 串连接 Concat
int Concat(HString *H, const HString *S, const HString *T){if(H->ch) free(H->ch); // 释放原有空间int len = S->length + T->length;H->ch = (char*) malloc(sizeof(char) * (len + 1));if(!H->ch) return 0;strcpy(H->ch, S->ch);strcat(H->ch, T->ch);H->length = len;return 1;
}

HString 连接操作需要 新分配内存,不能原地修改

时间复杂度 O(n + m),n = S.length, m = T.length

使用动态数组的好处是可以随机访问、容易管理

LString 的 Concat 的逻辑

  1. 先清空 H,避免内存泄漏
  2. 遍历 S,逐个结点复制到 H
  3. 遍历 T,逐个结点复制到 H
  4. 更新 H->length

⚠️ 注意:

  • LString 是 链式存储,所以不能直接拼接指针,而是要 复制新结点
  • 否则修改 H 会影响 ST
// 串连接 Concat
int Concat(LString *H, const LString *S, const LString *T){ClearString(H);LNode *tail = NULL;// 复制 SLNode *p = S->head;while(p){LNode *node = (LNode*) malloc(sizeof(LNode));if(!node) return 0;node->data = p->data;node->next = NULL;if(H->head == NULL){H->head = node;tail = node;} else {tail->next = node;tail = node;}p = p->next;}// 复制 Tp = T->head;while(p){LNode *node = (LNode*) malloc(sizeof(LNode));if(!node) return 0;node->data = p->data;node->next = NULL;if(H->head == NULL){H->head = node;tail = node;} else {tail->next = node;tail = node;}p = p->next;}H->length = S->length + T->length;return 1;
}

LString 连接操作需要 遍历两个链表,复制结点 → 时间复杂度 O(n + m)

与 HString 不同,链式存储不能直接用 strcat,必须分配新结点

这种方式保证 H 不会影响 S 和 T

求子串(SubString)操作

SubString 的逻辑

  1. 检查 poslen 是否有效:
    • pos >= 1pos <= S.length
    • len >= 0pos + len - 1 <= S.length
  2. 遍历源串,从 S.ch[pos-1] 开始,复制 len 个字符到 Sub.ch
  3. 设置 Sub.length = len

⚠️ 注意:SString 是定长顺序存储,Sub.length 不能超过 MAXSIZE

// 求子串
int SubString(SString *Sub, const SString *S, int pos, int len) {if(pos < 1 || pos > S->length || len < 0 || len > MAXSIZE || pos + len - 1 > S->length)return 0; // 参数非法for(int i=0;i<len;i++)Sub->ch[i] = S->ch[pos-1 + i]; // 注意数组下标从0开始Sub->length = len;return 1; // 成功
}

SubString 属于 处理类操作,常用于:

  • 截取文本
  • 查找匹配
  • 插入、替换等操作前的分割

时间复杂度 O(len)

HString / LString,逻辑类似,但 HString 可动态分配新数组,LString 需顺链复制节点

定位操作(Index / StrIndex)操作

定位操作逻辑(暴力匹配法)

  1. 检查子串长度是否为 0
    • 如果是 → 通常返回 1(空串在任意位置匹配)
  2. 遍历主串 S,从位置 i = 1 到 S.length - T.length + 1
  3. 对每个位置 i,比较 S 从 i 开始的连续 T.length 个字符是否等于 T
  4. 若匹配 → 返回 i
  5. 遍历完都不匹配 → 返回 0
// 定位操作(暴力匹配法)
int Index(const SString *S, const SString *T) {if(T->length == 0) return 1; // 空串匹配返回1for(int i=0; i <= S->length - T->length; i++) {int j;for(j=0;j<T->length;j++) {if(S->ch[i+j] != T->ch[j])break;}if(j == T->length)return i + 1; // 返回位置从1开始}return 0; // 未找到
}

Index / StrIndex 属于 处理类操作

暴力匹配法时间复杂度 O((n-m+1)*m),n = 主串长度,m = 子串长度

可以使用 KMP 算法 优化到 O(n+m)

HString / LString,逻辑类似,但 LString 需要顺链访问节点

替换操作(Replace)操作

Replace 的逻辑

  1. 检查 T 是否为空串
    • 若为空串 → 通常不进行替换
  2. 在主串 S 中循环查找 T 的位置(可使用 Index
  3. 找到后:
    • 删除位置 i 的 T 子串
    • 在位置 i 插入 V 子串
  4. 继续查找下一个 T,直到 S 中不再出现 T
  5. 注意:
    • SString 的长度不能超过 MAXSIZE
    • 删除 + 插入操作必须保持数组连续性
// 替换操作
void Replace(SString *S, const SString *T, const SString *V){if(T->length==0) return; // 空串不替换int pos=Index(S, T, 1);while(pos){Delete(S, pos, T->length);Insert(S, pos, V);pos=Index(S, T, pos + V->length); // 查找下一个}
}

Replace 属于 处理类操作

内部调用了 Index + Delete + Insert,所以时间复杂度取决于主串长度和被替换次数

HString / LString,操作类似,但 HString 可动态分配空间,LString 需要调整链表节点

插入(StrInsert)操作

StrInsert 的逻辑

  1. 检查插入位置是否合法:
    • 1 <= pos <= S.length + 1
  2. 检查插入后长度是否超过最大容量 MAXSIZE
  3. 将主串从 pos 开始的字符向后移动 T.length 个位置,为插入腾出空间
  4. 将 T.ch 的内容复制到 S.ch[pos-1 … pos-1+T.length-1]
  5. 更新 S.length = S.length + T.length

⚠️ 注意:SString 是顺序存储,数组必须连续,所以要从后向前移动字符,避免覆盖。

// 插入子串
int StrInsert(SString *S, int pos, const SString *T){if(pos<1 || pos>S->length+1 || S->length+T->length>MAXSIZE)return 0; // 插入位置非法或长度超出// 后移主串for(int i=S->length-1;i>=pos-1;i--){S->ch[i+T->length]=S->ch[i];}// 插入子串for(int i=0;i<T->length;i++){S->ch[pos-1+i]=T->ch[i];}S->length += T->length;return 1;
}

StrInsert 属于 处理类操作

时间复杂度 O(n),n = S.length - pos + 1(需要移动的字符数)

HString / LString

  • HString 可动态分配数组空间
  • LString 只需调整链表节点,插入更灵活

删除(StrDelete)操作

StrDelete 的逻辑

  1. 检查删除位置是否合法:
    • 1 <= pos <= S.length
    • 0 <= len <= S.length - pos + 1
  2. 将从 pos + len 开始的字符向前移动 len 个位置,覆盖被删除的部分
  3. 更新 S.length = S.length - len

⚠️ 注意:SString 是顺序存储,删除后数组仍然连续,不需要额外释放空间

// 删除子串
int StrDelete(SString *S, int pos, int len){if(pos<1 || pos>S->length || len<0 || pos+len-1>S->length)return 0; // 参数非法for(int i=pos-1+len; i<S->length; i++){S->ch[i-len] = S->ch[i]; // 前移覆盖}S->length -= len;return 1;
}

StrDelete 属于 处理类操作

时间复杂度 O(n),n = S.length - pos + 1(需要移动的字符数)

HString / LString

  • HString 可动态调整数组
  • LString 通过修改链表指针删除节点,操作更灵活

销毁(DestroyString)操作

DestroyString 的逻辑

  1. 对 SString(静态顺序存储):
    • 设置 S.length = 0
    • 可选:清空 S.ch 数组
    • 标记串不可再使用(逻辑上)
  2. 对 HString(动态顺序存储):
    • free(S.ch)
    • S.ch = NULL
    • S.length = 0
  3. 对链式存储(LString):
    • 顺序释放所有链表节点

⚠️ 注意:销毁后不能再对该串进行任何操作,否则会访问非法内存

void DestroyString(SString *S){S->length = 0;// 可选:清空数组内容// for(int i=0;i<MAXSIZE;i++) S->ch[i]='\0';
}

销毁串 vs 清空串

  • 清空串(ClearString):长度置 0,但数组/空间仍然可用
  • 销毁串(DestroyString):空间不可再用,需要重新分配或赋值

链式存储 LString:销毁需遍历链表释放每个节点

⚠️ 注意:

  • SString 的数组 ch[MAXSIZE]固定长度,在栈上或全局分配的,内存大小在编译时就已经确定。

  • ClearString

    void ClearString(SString *S){S->length = 0;
    }
    
    • 逻辑上:清空串,长度置 0
    • 内存仍然存在,可以继续赋值或插入新的内容
  • DestroyString

    void DestroyString(SString *S){S->length = 0;
    }
    
    • 对静态数组来说,其实无法真正释放数组空间,只能逻辑上标记为不可用
    • 所以代码看起来和 ClearString 一样

结论:对于 SString(静态数组),清空和销毁的区别主要在“逻辑含义”

  • 清空:可继续使用
  • 销毁:逻辑上不再使用,不能再调用操作函数

操作集合

#include <stdio.h>
#define MAXSIZE 255typedef struct {char ch[MAXSIZE];int length;
} SString;// 1. 串赋值 StrAssign
int StrAssign(SString *S, const char *chars){int i=0;while(chars[i]!='\0'){if(i>=MAXSIZE) return 0;S->ch[i]=chars[i];i++;}S->length=i;return 1;
}// 2. 串复制 StrCopy
int StrCopy(SString *T, const SString *S){if(S->length>MAXSIZE) return 0;for(int i=0;i<S->length;i++)T->ch[i]=S->ch[i];T->length=S->length;return 1;
}// 3. 判空 StrEmpty
int StrEmpty(const SString *S){return S->length == 0;
}// 4. 求串长 StrLength
int StrLength(const SString *S){return S->length;
}// 5. 串比较 StrCompare
int StrCompare(const SString *S, const SString *T){int i=0;while(i<S->length && i<T->length){if(S->ch[i] != T->ch[i])return S->ch[i]-T->ch[i];i++;}return S->length - T->length;
}// 6. 清空串 ClearString
void ClearString(SString *S){S->length = 0;
}// 7. 串连接 Concat
int Concat(SString *T, const SString *S1, const SString *S2){if(S1->length + S2->length > MAXSIZE) return 0;for(int i=0;i<S1->length;i++) T->ch[i]=S1->ch[i];for(int i=0;i<S2->length;i++) T->ch[S1->length+i]=S2->ch[i];T->length = S1->length + S2->length;return 1;
}// 8. 求子串 SubString
int SubString(SString *Sub, const SString *S, int pos, int len){if(pos<1 || pos>S->length || len<0 || pos+len-1>S->length) return 0;for(int i=0;i<len;i++)Sub->ch[i] = S->ch[pos-1+i];Sub->length = len;return 1;
}// 9. 定位操作 Index / StrIndex
int Index(const SString *S, const SString *T, int start){if(T->length==0) return start;for(int i=start-1;i<=S->length-T->length;i++){int j;for(j=0;j<T->length;j++)if(S->ch[i+j]!=T->ch[j]) break;if(j==T->length) return i+1;}return 0;
}// 10. 替换操作 Replace
void Replace(SString *S, const SString *T, const SString *V){if(T->length==0) return;int pos = Index(S,T,1);while(pos){Delete(S,pos,T->length);Insert(S,pos,V);pos = Index(S,T,pos+V->length);}
}// 11. 插入操作 StrInsert
int Insert(SString *S, int pos, const SString *T){if(pos<1 || pos>S->length+1 || S->length+T->length>MAXSIZE) return 0;for(int i=S->length-1;i>=pos-1;i--)S->ch[i+T->length] = S->ch[i];for(int i=0;i<T->length;i++)S->ch[pos-1+i]=T->ch[i];S->length += T->length;return 1;
}// 12. 删除操作 StrDelete
int Delete(SString *S, int pos, int len){if(pos<1||pos>S->length||len<0||pos+len-1>S->length) return 0;for(int i=pos-1+len;i<S->length;i++)S->ch[i-len] = S->ch[i];S->length -= len;return 1;
}// 13. 销毁 DestroyString
void DestroyString(SString *S){S->length = 0;// 静态顺序存储数组空间不可释放,只能逻辑上销毁
}int main(){SString S, T, U;StrAssign(&S,"abcabc");StrAssign(&T,"ab");StrAssign(&U,"XY");printf("替换前S=");for(int i=0;i<S.length;i++) printf("%c",S.ch[i]);printf("\n");Replace(&S,&T,&U);printf("替换后S=");for(int i=0;i<S.length;i++) printf("%c",S.ch[i]);printf("\n");return 0;
}

程序运行结果如下:

在这里插入图片描述

http://www.dtcms.com/a/349517.html

相关文章:

  • 36 NoSQL 注入
  • Docker 部署 GitLab 并开启 SSH 使用详解
  • 【Java后端】Java 多线程:从原理到实战,再到高频面试题
  • Claude Code 使用及配置智能体
  • 【科研绘图系列】R语言绘制代谢物与临床表型相关性的森林图
  • 从零到一:现代化充电桩App的React前端参考
  • 将FGUI的Shader全部预热后,WebGL平台没有加载成功
  • 基于MalConv的恶意软件检测系统设计与实现
  • 大模型 transformer 步骤
  • 《拉康精神分析学中的欲望辩证法:能指的拓扑学与主体的解构性重构》
  • 计算机大数据技术不会?医院体检数据可视化分析系统Django+Vue全栈方案
  • 不止效率工具:AI 在文化创作中如何重构 “灵感逻辑”?
  • 【DFS 或 BFS 或拓扑排序 - LeetCode】329. 矩阵中的最长递增路径
  • 【图像算法 - 23】工业应用:基于深度学习YOLO12与OpenCV的仪器仪表智能识别系统
  • 基于视觉的果园无人机导航:一种基于干预模仿学习与VAE控制器的真实世界验证
  • 机器人中的李代数是什么
  • 抖音多账号运营新范式:巨推AI如何解锁流量矩阵的商业密码
  • 量子计算驱动的Python医疗诊断编程前沿展望(下)
  • 数据结构:单向链表的逆置;双向循环链表;栈,输出栈,销毁栈;顺序表和链表的区别和优缺点;0825
  • 平安产险青海分公司启动2025年“乡风文明100行动” 首站落地海东市乐都区土官沟村
  • 【C++详解】哈希表概念与实现 开放定址法和链地址法、处理哈希冲突、哈希函数介绍
  • Redis缓存雪崩缓存击穿缓存穿透的处理方式
  • [React]Antd Upload组件上传多个文件
  • 阿里云安装postgre数据库
  • Vim 的 :term命令:终端集成的终极指南
  • 中介者模式及优化
  • Flink 状态 RocksDBListState(写入时的Merge优化)
  • 元宇宙与个人生活:重构日常体验的数字新维度
  • 技术攻坚与安全兜底——消防智能仓储立库管理系统的国产化硬核实力
  • ADB 调试工具的学习[特殊字符]