数据结构之串

打印 上一主题 下一主题

主题 1768|帖子 1768|积分 5304

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
数据结构之串(String)

在数据结构中,串(String) 是一种用于表示和操纵字符序列的结构。它是最根本的线性表类型之一,广泛应用于文本处理、模式匹配、字符串操纵等领域。

1. 串的界说

是由零个或多个字符组成的有限序列,也可以称为字符串。比方:


  • 空串:长度为零的串。
  • 非空串:如 "abc" 或 "数据结构"。
一个勾通常表示为:
  1. S = "a1a2a3...an" (n >= 0)
复制代码
其中,a1, a2, ..., an 是串中的字符,n 为串的长度。字符可以是字母、数字、符号、空格等任意字符。
串的干系概念

  • 空串 (Null String)

    • 长度为 0 的串。
    • 空串与 NULL 不同,空串是合法的字符串对象,只是内容为空。

  • 空格串 (Blank String)

    • 由一个或多个空格字符组成的串,比方 " "。
    • 空格串的长度大于等于 1。

  • 子串 (Substring)

    • 串中任意连续字符组成的子序列称为该串的“子串”。
    • 子串可为空串。比方,"abc" 的子串包括 "a", "ab", "bc", 以及空串 ""。

  • 主串 (Master String)

    • 包含子串的串称为“主串”。比方,"abcde" 是子串 "bcd" 的主串。

  • 串的长度 (Length)

    • 串中所包含字符的个数,不包含结束标记 \0。

  • 前缀 (Prefix)

    • 一个字符串的前缀是从第一个字符开始到某个字符位置的子串。比方,"abc" 的前缀有:"","a","ab"。

  • 后缀 (Suffix)

    • 一个字符串的后缀是从某个字符位置到末了一个字符的子串。比方,"abc" 的后缀有:"","c","bc"。


2. 串的存储结构

根据串的存储形式,常见的实现方式有以下几种:
2.1 顺序存储



  • 将串的字符按顺序存储在一块连续的存储空间中。
  • 优点:支持随机访问,操纵效率高。
  • 缺点:插入和删除操纵需要移动大量字符,效率较低。
  1. char str[100]; // 单纯的字符数组
复制代码
2.2 链式存储



  • 使用链表存储串的字符,每个节点存储一个字符。
  • 优点:支持动态扩展,插入和删除操纵较快。
  • 缺点:占用额外的存储空间,随机访问效率较低。
  1. typedef struct StringNode {
  2.     char data;
  3.     struct StringNode *next;
  4. } StringNode;
复制代码
2.3 索引存储



  • 将串分段存储,同时使用索引表记载每段的起始地址。
  • 优点:得当处理超大字符串,分段加载,节流内存。
  • 缺点:管理复杂度较高。
  1. #define MAX_SEGMENT_LENGTH 50 // 每个段的最大长度
  2. #define MAX_SEGMENTS 10       // 最大段数
  3. typedef struct {
  4.     char *data;              // 存储段的数据
  5. } Segment;
  6. typedef struct {
  7.     Segment segments[MAX_SEGMENTS]; // 存储多个段
  8.     int segmentCount;               // 当前段数
  9. } IndexString;
复制代码

3. 串的常见操纵

3.1 根本操纵

3.1.1 创建串

初始化字符串。
  1. typedef struct {
  2.     char mem[1024]; // 用于存储字符的数组
  3.     int curSize;    // 当前字符串长度
  4. } String, *LPSTR;
  5. // 创建字符串
  6. LPSTR createstring(const char *str) {
  7.     LPSTR pstr = (LPSTR)malloc(sizeof(String));
  8.     assert(pstr);
  9.     memset(pstr->mem, '\0', 1024); // 初始化数组
  10.     int count = 0;
  11.     while (str[count] != '\0' && count < 1024) {
  12.         pstr->mem[count] = str[count];
  13.         count++;
  14.     }
  15.     pstr->curSize = count; // 更新当前长度
  16.     return pstr;
  17. }
  18. // 打印字符串
  19. void printstring(LPSTR pstr) {
  20.     for (int i = 0; i < pstr->curSize; i++) {
  21.         putchar(pstr->mem[i]);
  22.     }
  23.     putchar('\n');
  24. }
复制代码
3.1.2 求串长度

返回字符串中字符的个数。
  1. // 求串长度
  2. int getlength(LPSTR pstr) {
  3.     return pstr->curSize;
  4. }
复制代码
3.1.3 勾通接

将两个字符串拼接为一个字符串。
  1. // 串连接
  2. void concatstring(LPSTR dest, const char *src) {
  3.     int srcLen = strlen(src);
  4.     if (dest->curSize + srcLen >= 1024) {
  5.         printf("字符串过长,无法连接!\n");
  6.         return;
  7.     }
  8.     for (int i = 0; i < srcLen; i++) {
  9.         dest->mem[dest->curSize + i] = src[i];
  10.     }
  11.     dest->curSize += srcLen;
  12. }
复制代码
3.1.4 串比较

比较两个字符串的大小(字典顺序)。
  1. // 串比较
  2. int comparestring(LPSTR str1, LPSTR str2) {
  3.     return strcmp(str1->mem, str2->mem);
  4. }
复制代码
3.1.5 串拷贝

将一个字符串复制到另一个字符串中。
  1. // 串拷贝
  2. void copystring(LPSTR dest, LPSTR src) {
  3.     memset(dest->mem, '\0', 1024); // 清空目标串
  4.     for (int i = 0; i < src->curSize; i++) {
  5.         dest->mem[i] = src->mem[i];
  6.     }
  7.     dest->curSize = src->curSize;
  8. }
复制代码

3.2 高级操纵

3.2.1 查找子串

BF算法(Brute Force)

BF算法,即暴力(Brute Force)算法,是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继承比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出末了的匹配效果。BF算法是一种蛮力算法。
  1. // 查找子串 - BF算法
  2. int findsubstringBF(LPSTR mainStr, const char *pattern) {
  3.     int m = mainStr->curSize; // 主串长度
  4.     int n = strlen(pattern);  // 模式串长度
  5.     for (int i = 0; i <= m - n; i++) {
  6.         int j = 0;
  7.         while (j < n && mainStr->mem[i + j] == pattern[j]) {
  8.             j++;
  9.         }
  10.         if (j == n) {
  11.             return i; // 返回匹配的起始位置
  12.         }
  13.     }
  14.     return -1; // 未找到
  15. }
复制代码
KMP算法(Knuth-Morris-Pratt)

KMP算法是一种高效的字符串匹配算法,它的核心思想是使用已匹配的部分信息来制止重复匹配,从而提拔匹配效率。相较于朴素的暴力匹配算法(BF算法),KMP在最坏情况下的时间复杂度为 O(m + n),其中 m 是主串长度,n 是模式串长度。

核心思想


  • 前缀表 (Partial Match Table, PMT)

    • 前缀表存储的是模式串中每个位置之前的子串的“前缀”和“后缀”的最大匹配长度。
    • 前缀:子串的前面部分。
    • 后缀:子串的后面部分。
    • 例:"ababaca" 的前缀表如下:
      1. 模式串:    a  b  a  b  a  c  a
      2. 前缀表PMT: 0  0  1  2  3  0  1
      复制代码
      含义:在模式串的第 i 个字符时,前缀与后缀能匹配的最大长度为 PMT

  • 通过前缀表优化匹配

    • 当匹配失败时,不需要回退主串的匹配位置,而是通过前缀表直接跳转到模式串的某个位置继承匹配。
    • 如许可以制止重复匹配,从而进步效率。

构造前缀表

前缀表(也叫 next 数组)是 KMP 算法的核心,描述了模式串中每个字符之前的部分匹配信息。
  1. // 计算模式串的前缀表 (next 数组)
  2. void computePrefixTable(const char *pattern, int *next, int n) {
  3.     next[0] = 0; // 第一个字符的前缀长度总是 0
  4.     int len = 0; // 当前最长前缀的长度
  5.     int i = 1;
  6.     while (i < n) {
  7.         if (pattern[i] == pattern[len]) {
  8.             len++;
  9.             next[i] = len;
  10.             i++;
  11.         } else {
  12.             if (len > 0) {
  13.                 len = next[len - 1]; // 回溯到上一个最长前缀的位置
  14.             } else {
  15.                 next[i] = 0;
  16.                 i++;
  17.             }
  18.         }
  19.     }
  20. }
复制代码
示例:计算 "ababaca" 的前缀表
  1. const char *pattern = "ababaca";
  2. int next[7];
  3. computePrefixTable(pattern, next, strlen(pattern));
  4. for (int i = 0; i < 7; i++) {
  5.     printf("%d ", next[i]);  // 输出: 0 0 1 2 3 0 1
  6. }
复制代码
KMP 主串匹配

通过前缀表实现高效的字符串匹配。
  1. // KMP 模式匹配
  2. int KMP(const char *text, const char *pattern) {
  3.     int m = strlen(text);    // 主串长度
  4.     int n = strlen(pattern); // 模式串长度
  5.     // 构造前缀表
  6.     int *next = (int *)malloc(sizeof(int) * n);
  7.     computePrefixTable(pattern, next, n);
  8.     int i = 0; // 主串索引
  9.     int j = 0; // 模式串索引
  10.     while (i < m) {
  11.         if (text[i] == pattern[j]) {
  12.             i++;
  13.             j++;
  14.         }
  15.         if (j == n) {
  16.             free(next);
  17.             return i - j; // 匹配成功,返回匹配起始位置
  18.         } else if (i < m && text[i] != pattern[j]) {
  19.             if (j > 0) {
  20.                 j = next[j - 1]; // 利用前缀表回溯
  21.             } else {
  22.                 i++;
  23.             }
  24.         }
  25.     }
  26.     free(next);
  27.     return -1; // 未找到匹配
  28. }
复制代码
测试代码

以下是完整测试代码,展示了如何使用 KMP 算法进行模式匹配:
  1. #include <stdio.h>#include <string.h>#include <stdlib.h>// 计算模式串的前缀表 (next 数组)
  2. void computePrefixTable(const char *pattern, int *next, int n) {
  3.     next[0] = 0; // 第一个字符的前缀长度总是 0
  4.     int len = 0; // 当前最长前缀的长度
  5.     int i = 1;
  6.     while (i < n) {
  7.         if (pattern[i] == pattern[len]) {
  8.             len++;
  9.             next[i] = len;
  10.             i++;
  11.         } else {
  12.             if (len > 0) {
  13.                 len = next[len - 1]; // 回溯到上一个最长前缀的位置
  14.             } else {
  15.                 next[i] = 0;
  16.                 i++;
  17.             }
  18.         }
  19.     }
  20. }
  21. // KMP 模式匹配
  22. int KMP(const char *text, const char *pattern) {
  23.     int m = strlen(text);    // 主串长度
  24.     int n = strlen(pattern); // 模式串长度
  25.     // 构造前缀表
  26.     int *next = (int *)malloc(sizeof(int) * n);
  27.     computePrefixTable(pattern, next, n);
  28.     int i = 0; // 主串索引
  29.     int j = 0; // 模式串索引
  30.     while (i < m) {
  31.         if (text[i] == pattern[j]) {
  32.             i++;
  33.             j++;
  34.         }
  35.         if (j == n) {
  36.             free(next);
  37.             return i - j; // 匹配成功,返回匹配起始位置
  38.         } else if (i < m && text[i] != pattern[j]) {
  39.             if (j > 0) {
  40.                 j = next[j - 1]; // 利用前缀表回溯
  41.             } else {
  42.                 i++;
  43.             }
  44.         }
  45.     }
  46.     free(next);
  47.     return -1; // 未找到匹配
  48. }
  49. int main() {    const char *text = "ababcabcacbab";    // 主串    const char *pattern = "abcac";        // 模式串    int pos = KMP(text, pattern);    if (pos != -1) {        printf("模式串在主串中的起始位置为: %d\n", pos); // 输出: 5    } else {        printf("模式串未找到!\n");    }    return 0;}
复制代码
3.2.2 插入子串

在指定位置插入一个字符串。
  1. // 插入子串
  2. void insertstring(LPSTR pstr, const char *str, int pos) {
  3.     int len = strlen(str);
  4.     if (pos < 0 || pos > pstr->curSize || pstr->curSize + len >= 1024) {
  5.         printf("插入失败:无效下标或字符串过长!\n");
  6.         return;
  7.     }
  8.     // 移动原有字符腾出空间
  9.     for (int i = pstr->curSize - 1; i >= pos; i--) {
  10.         pstr->mem[i + len] = pstr->mem[i];
  11.     }
  12.     // 插入新字符串
  13.     for (int i = 0; i < len; i++) {
  14.         pstr->mem[pos + i] = str[i];
  15.     }
  16.     pstr->curSize += len;
  17. }
复制代码
3.2.3 删除子串

区间删除:直接删除指定范围的字符。
  1. // 删除子串
  2. void deletestring(LPSTR pstr, int start, int end) {
  3.     if (start < 0 || end >= pstr->curSize || start > end) {
  4.         printf("删除失败:区间不合法!\n");
  5.         return;
  6.     }
  7.     int count = end - start + 1;
  8.     // 后续字符前移
  9.     for (int i = start; i < pstr->curSize - count; i++) {
  10.         pstr->mem[i] = pstr->mem[i + count];
  11.     }
  12.     pstr->curSize -= count;
  13.     // 清除多余字符
  14.     for (int i = pstr->curSize; i < pstr->curSize + count; i++) {
  15.         pstr->mem[i] = '\0';
  16.     }
  17. }
复制代码
匹配删除:删除与某个模式串匹配的部分。
匹配删除操纵需要先找到模式串在主串中的位置,然后将其删除。

  • 使用子串查找算法(如 BF 算法)确定模式串的位置。
  • 假如找到模式串,调用区间删除函数 deletestring 删除该部分内容。
  1. // 查找子串位置 - BF算法
  2. int findsubstringBF(LPSTR mainStr, const char *pattern) {
  3.     int m = mainStr->curSize; // 主串长度
  4.     int n = strlen(pattern);  // 模式串长度
  5.     for (int i = 0; i <= m - n; i++) {
  6.         int j = 0;
  7.         while (j < n && mainStr->mem[i + j] == pattern[j]) {
  8.             j++;
  9.         }
  10.         if (j == n) {
  11.             return i; // 返回匹配的起始位置
  12.         }
  13.     }
  14.     return -1; // 未找到
  15. }
  16. // 匹配删除
  17. void matchdelete(LPSTR pstr, const char *pattern) {
  18.     int start = findsubstringBF(pstr, pattern);  // 找到模式串的起始位置
  19.     if (start == -1) {
  20.         printf("未找到模式串,无法删除!\n");
  21.         return;
  22.     }
  23.     int end = start + strlen(pattern) - 1;  // 计算模式串的结束位置
  24.     // 调用区间删除函数
  25.     deletestring(pstr, start, end);
  26. }
复制代码
3.3.4 替换子串

将子串替换为新的内容。
  1. // 替换子串
  2. void replacestring(LPSTR pstr, const char *oldStr, const char *newStr) {
  3.     int pos = findsubstringBF(pstr, oldStr);
  4.     if (pos == -1) {
  5.         printf("子串未找到,无法替换!\n");
  6.         return;
  7.     }
  8.     // 删除旧子串
  9.     deletestring(pstr, pos, pos + strlen(oldStr) - 1);
  10.     // 插入新子串
  11.     insertstring(pstr, newStr, pos);
  12. }
复制代码
4. 串的存储实现与代码示例

以下代码展示了基于顺序存储的一些根本串操纵的实现,包括创建串、插入操纵、区间删除等:
  1. #include <stdio.h>#include <stdlib.h>#include <string.h>#include <assert.h>// 界说字符串数据结构typedef struct {
  2.     char mem[1024]; // 用于存储字符的数组
  3.     int curSize;    // 当前字符串长度
  4. } String, *LPSTR;
  5. // 创建字符串
  6. LPSTR createstring(const char *str) {
  7.     LPSTR pstr = (LPSTR)malloc(sizeof(String));
  8.     assert(pstr);
  9.     memset(pstr->mem, '\0', 1024); // 初始化数组
  10.     int count = 0;
  11.     while (str[count] != '\0' && count < 1024) {
  12.         pstr->mem[count] = str[count];
  13.         count++;
  14.     }
  15.     pstr->curSize = count; // 更新当前长度
  16.     return pstr;
  17. }
  18. // 打印字符串
  19. void printstring(LPSTR pstr) {
  20.     for (int i = 0; i < pstr->curSize; i++) {
  21.         putchar(pstr->mem[i]);
  22.     }
  23.     putchar('\n');
  24. }
  25. // 求串长度
  26. int getlength(LPSTR pstr) {
  27.     return pstr->curSize;
  28. }
  29. // 串连接
  30. void concatstring(LPSTR dest, const char *src) {
  31.     int srcLen = strlen(src);
  32.     if (dest->curSize + srcLen >= 1024) {
  33.         printf("字符串过长,无法连接!\n");
  34.         return;
  35.     }
  36.     for (int i = 0; i < srcLen; i++) {
  37.         dest->mem[dest->curSize + i] = src[i];
  38.     }
  39.     dest->curSize += srcLen;
  40. }
  41. // 串比较
  42. int comparestring(LPSTR str1, LPSTR str2) {
  43.     return strcmp(str1->mem, str2->mem);
  44. }
  45. // 串拷贝
  46. void copystring(LPSTR dest, LPSTR src) {
  47.     memset(dest->mem, '\0', 1024); // 清空目标串
  48.     for (int i = 0; i < src->curSize; i++) {
  49.         dest->mem[i] = src->mem[i];
  50.     }
  51.     dest->curSize = src->curSize;
  52. }
  53. // 查找子串 - BF算法
  54. int findsubstringBF(LPSTR mainStr, const char *pattern) {
  55.     int m = mainStr->curSize; // 主串长度
  56.     int n = strlen(pattern);  // 模式串长度
  57.     for (int i = 0; i <= m - n; i++) {
  58.         int j = 0;
  59.         while (j < n && mainStr->mem[i + j] == pattern[j]) {
  60.             j++;
  61.         }
  62.         if (j == n) {
  63.             return i; // 返回匹配的起始位置
  64.         }
  65.     }
  66.     return -1; // 未找到
  67. }
  68. // 插入子串
  69. void insertstring(LPSTR pstr, const char *str, int pos) {
  70.     int len = strlen(str);
  71.     if (pos < 0 || pos > pstr->curSize || pstr->curSize + len >= 1024) {
  72.         printf("插入失败:无效下标或字符串过长!\n");
  73.         return;
  74.     }
  75.     // 移动原有字符腾出空间
  76.     for (int i = pstr->curSize - 1; i >= pos; i--) {
  77.         pstr->mem[i + len] = pstr->mem[i];
  78.     }
  79.     // 插入新字符串
  80.     for (int i = 0; i < len; i++) {
  81.         pstr->mem[pos + i] = str[i];
  82.     }
  83.     pstr->curSize += len;
  84. }
  85. // 删除子串
  86. void deletestring(LPSTR pstr, int start, int end) {
  87.     if (start < 0 || end >= pstr->curSize || start > end) {
  88.         printf("删除失败:区间不合法!\n");
  89.         return;
  90.     }
  91.     int count = end - start + 1;
  92.     // 后续字符前移
  93.     for (int i = start; i < pstr->curSize - count; i++) {
  94.         pstr->mem[i] = pstr->mem[i + count];
  95.     }
  96.     pstr->curSize -= count;
  97.     // 清除多余字符
  98.     for (int i = pstr->curSize; i < pstr->curSize + count; i++) {
  99.         pstr->mem[i] = '\0';
  100.     }
  101. }
  102. // 替换子串
  103. void replacestring(LPSTR pstr, const char *oldStr, const char *newStr) {
  104.     int pos = findsubstringBF(pstr, oldStr);
  105.     if (pos == -1) {
  106.         printf("子串未找到,无法替换!\n");
  107.         return;
  108.     }
  109.     // 删除旧子串
  110.     deletestring(pstr, pos, pos + strlen(oldStr) - 1);
  111.     // 插入新子串
  112.     insertstring(pstr, newStr, pos);
  113. }
  114. // 主函数测试int main() {    // 创建字符串    LPSTR str1 = createstring("Hello World");    printf("原始字符串: ");    printstring(str1);    // 求字符串长度    printf("字符串长度: %d\n", getlength(str1));    // 勾通接    concatstring(str1, "!!!");    printf("连接后的字符串: ");    printstring(str1);    // 插入子串    insertstring(str1, " Beautiful", 5);    printf("插入后的字符串: ");    printstring(str1);    // 删除子串    deletestring(str1, 6, 15);    printf("删除后的字符串: ");    printstring(str1);    // 替换子串    replacestring(str1, "World", "Universe");    printf("替换后的字符串: ");    printstring(str1);    // 查找子串    int pos = findsubstringBF(str1, "Universe");    if (pos != -1) {        printf("子串 'Universe' 的起始位置: %d\n", pos);    } else {        printf("子串 'Universe' 未找到!\n");    }    // 串拷贝    LPSTR str2 = createstring("");    copystring(str2, str1);    printf("拷贝后的字符串: ");    printstring(str2);    // 串比较    const char *compareResult = comparestring(str1, str2) == 0 ? "相等" : "不相等";    printf("str1 和 str2 比较效果: %s\n", compareResult);    // 开释内存    free(str1);    free(str2);    return 0;}
复制代码

5. 串的现实应用


  • 文本编辑器:字符串的插入、删除、查找、替换等操纵。
  • 网络传输:处理传输的数据包和协议,比方 HTTP 报文。
  • 搜索引擎:关键词匹配、统计、自动补全等。
  • DNA 序列分析:基因比对、模式匹配。
  • 文件路径剖析:操纵体系中对文件路径的处理。
  • 正则表达式:复杂模式查找与替换。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

天津储鑫盛钢材现货供应商

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表