跳转至

BB3-判断子串s2是否在母串s1中匹配,返回第一个匹配位置的索引

代码实现

C
/**
 * 判断子串s2是否在母串s1中匹配,返回第一个匹配位置的索引
 * @param s1 母串(被搜索的字符串)
 * @param s2 子串(要搜索的字符串)
 * @return 匹配成功返回第一个匹配字符的索引,失败返回-1
 */
int find(char s1[], char s2[]) {
    // 边界条件检查
    if (s1 == NULL || s2 == NULL) {
        return -1;
    }

    // 空子串处理:空串在任何字符串中都匹配,位置为0
    if (s2[0] == '\0') {
        return 0;
    }

    // 从母串的每个位置开始尝试匹配
    int pos = 0;  // 当前尝试匹配的起始位置

    while (s1[pos] != '\0') {  // 遍历母串的每个可能起始位置
        int p1 = pos;  // 母串当前比较位置
        int p2 = 0;    // 子串当前比较位置

        // 在当前位置尝试匹配整个子串
        while (s1[p1] != '\0' && s2[p2] != '\0') {
            if (s1[p1] == s2[p2]) {
                // 字符匹配,继续比较下一个字符
                p1++;
                p2++;
            } else {
                // 字符不匹配,跳出内层循环
                break;
            }
        }

        // 检查是否完全匹配
        if (s2[p2] == '\0') {
            // 子串已完全匹配,返回匹配起始位置
            return pos;
        }

        // 当前位置匹配失败,尝试下一个起始位置
        pos++;
    }

    // 遍历完所有可能位置都未找到匹配
    return -1;
}
C
/**
 * 优化版本1:添加长度预检查
 * @param s1 母串
 * @param s2 子串
 * @return 匹配成功返回索引,失败返回-1
 */
int find_optimized(char s1[], char s2[]) {
    if (s1 == NULL || s2 == NULL) {
        return -1;
    }

    if (s2[0] == '\0') {
        return 0;
    }

    int len1 = strlen(s1);
    int len2 = strlen(s2);

    // 如果子串比母串长,不可能匹配
    if (len2 > len1) {
        return -1;
    }

    // 只需要检查到 len1 - len2 的位置
    for (int pos = 0; pos <= len1 - len2; pos++) {
        int p1 = pos;
        int p2 = 0;

        // 尝试匹配
        while (s1[p1] != '\0' && s2[p2] != '\0' && s1[p1] == s2[p2]) {
            p1++;
            p2++;
        }

        // 检查是否完全匹配
        if (s2[p2] == '\0') {
            return pos;
        }
    }

    return -1;
}

朴素字符串匹配算法(Brute Force)

C
/**
 * 朴素字符串匹配算法(Brute Force)
 * @param text 母串(文本)
 * @param pattern 子串(模式串)
 * @return 匹配成功返回索引,失败返回-1
 */
int brute_force_match(char text[], char pattern[]) {
    if (text == NULL || pattern == NULL) {
        return -1;
    }

    int text_len = strlen(text);
    int pattern_len = strlen(pattern);

    if (pattern_len == 0) {
        return 0;
    }

    if (pattern_len > text_len) {
        return -1;
    }

    // 朴素匹配算法
    for (int i = 0; i <= text_len - pattern_len; i++) {
        int j;
        // 检查从位置i开始是否匹配
        for (j = 0; j < pattern_len; j++) {
            if (text[i + j] != pattern[j]) {
                break;
            }
        }

        // 如果j等于pattern_len,说明完全匹配
        if (j == pattern_len) {
            return i;
        }
    }

    return -1;  // 未找到匹配
}

算法执行流程图解

匹配成功示例:

Text Only
母串s1:"abcdef"
子串s2:"cde"

执行过程:
pos=0: a vs c → 不匹配,pos=1
pos=1: b vs c → 不匹配,pos=2
pos=2: c vs c → 匹配,p1=3, p2=1
       d vs d → 匹配,p1=4, p2=2
       e vs e → 匹配,p1=5, p2=3
       p2='\0' → 完全匹配,返回pos=2

匹配失败示例:

Text Only
母串s1:"abcdef"
子串s2:"cfg"

执行过程:
pos=0: a vs c → 不匹配,pos=1
pos=1: b vs c → 不匹配,pos=2
pos=2: c vs c → 匹配,p1=3, p2=1
       d vs f → 不匹配,跳出
pos=3: d vs c → 不匹配,pos=4
pos=4: e vs c → 不匹配,pos=5
pos=5: f vs c → 不匹配,结束
返回-1

算法状态变化图解

Text Only
母串:"hello world"
子串:"wor"

匹配过程:
pos=0: h≠w → 失败
pos=1: e≠w → 失败
pos=2: l≠w → 失败
pos=3: l≠w → 失败
pos=4: o≠w → 失败
pos=5: 空格≠w → 失败
pos=6: w=w → 继续
       o=o → 继续
       r=r → 继续
       '\0'='\0' → 成功,返回6

复杂度分析

时间复杂度:O(m×n)

  • 最坏情况:O(m×n),其中m是母串长度,n是子串长度
  • 最好情况:O(m),第一个字符就不匹配
  • 平均情况:O(m×n),但在实际应用中通常表现较好
  • 举例:母串长度1000,子串长度10,最坏情况下需要比较10000次

空间复杂度:O(1)

  • 辅助变量:只使用常数个变量(pos, p1, p2等)
  • 无额外存储:不需要额外的数据结构
  • 总体空间复杂度O(1)

KMP算法(Knuth-Morris-Pratt)- 更高效的字符串匹配

C
/**
 * KMP算法(Knuth-Morris-Pratt)- 更高效的字符串匹配
 * 时间复杂度:O(m+n)
 */
int kmp_match(char text[], char pattern[]) {
    if (text == NULL || pattern == NULL) return -1;

    int text_len = strlen(text);
    int pattern_len = strlen(pattern);

    if (pattern_len == 0) return 0;
    if (pattern_len > text_len) return -1;

    // 计算next数组(部分匹配表)
    int next[pattern_len];
    next[0] = 0;
    int j = 0;

    for (int i = 1; i < pattern_len; i++) {
        while (j > 0 && pattern[i] != pattern[j]) {
            j = next[j - 1];
        }
        if (pattern[i] == pattern[j]) {
            j++;
        }
        next[i] = j;
    }

    // KMP匹配过程
    j = 0;
    for (int i = 0; i < text_len; i++) {
        while (j > 0 && text[i] != pattern[j]) {
            j = next[j - 1];
        }
        if (text[i] == pattern[j]) {
            j++;
        }
        if (j == pattern_len) {
            return i - j + 1;
        }
    }

    return -1;
}

Boyer-Moore算法 - 另一种高效的字符串匹配算法

C
/**
 * Boyer-Moore算法 - 另一种高效的字符串匹配算法
 */
int boyer_moore_match(char text[], char pattern[]) {
    // 实现较为复杂,这里给出基本框架
    if (text == NULL || pattern == NULL) return -1;

    int text_len = strlen(text);
    int pattern_len = strlen(pattern);

    if (pattern_len == 0) return 0;
    if (pattern_len > text_len) return -1;

    // 预处理:计算坏字符表和好后缀表
    // ...(省略具体实现)

    // 匹配过程
    // ...(省略具体实现)

    return -1;  // 简化版本
}

错误处理完善版本

C
/**
 * 安全版本:添加完整的错误处理和边界检查
 */
int find_safe(char s1[], char s2[]) {
    // 参数验证
    if (s1 == NULL) {
        printf("错误:母串为空指针\n");
        return -1;
    }

    if (s2 == NULL) {
        printf("错误:子串为空指针\n");
        return -1;
    }

    // 空子串处理
    if (s2[0] == '\0') {
        return 0;  // 空串在任何位置都匹配
    }

    int len1 = strlen(s1);
    int len2 = strlen(s2);

    // 长度检查
    if (len1 < 0 || len2 < 0) {
        printf("错误:字符串长度异常\n");
        return -1;
    }

    // 子串不能比母串长
    if (len2 > len1) {
        return -1;
    }

    // 匹配过程
    for (int pos = 0; pos <= len1 - len2; pos++) {
        int match = 1;  // 假设匹配成功

        for (int i = 0; i < len2; i++) {
            if (s1[pos + i] != s2[i]) {
                match = 0;  // 匹配失败
                break;
            }
        }

        if (match) {
            return pos;  // 返回匹配位置
        }
    }

    return -1;  // 未找到匹配
}

性能测试对比

C
/**
 * 性能测试函数
 */
void performance_comparison() {
    char text[10000];
    char pattern[] = "search";

    // 构造测试文本
    for (int i = 0; i < 9990; i++) {
        text[i] = 'a' + (i % 26);
    }
    strcpy(text + 5000, "search");
    text[9999] = '\0';

    clock_t start, end;

    // 测试朴素算法
    start = clock();
    for (int i = 0; i < 10000; i++) {
        find(text, pattern);
    }
    end = clock();
    printf("朴素算法耗时: %ld\n", end - start);

    // 测试KMP算法
    start = clock();
    for (int i = 0; i < 10000; i++) {
        kmp_match(text, pattern);
    }
    end = clock();
    printf("KMP算法耗时: %ld\n", end - start);
}

实际应用示例

C
/**
 * 实用函数:在文件中搜索关键词
 */
int search_in_file(const char* filename, const char* keyword) {
    FILE* file = fopen(filename, "r");
    if (file == NULL) {
        printf("无法打开文件: %s\n", filename);
        return -1;
    }

    char line[1000];
    int line_number = 0;

    while (fgets(line, sizeof(line), file) != NULL) {
        line_number++;
        if (find(line, keyword) != -1) {
            printf("在第%d行找到关键词: %s", line_number, line);
            fclose(file);
            return line_number;
        }
    }

    fclose(file);
    return -1;  // 未找到
}

/**
 * 多个子串搜索
 */
int find_multiple(char s1[], char patterns[][100], int pattern_count) {
    for (int i = 0; i < pattern_count; i++) {
        int pos = find(s1, patterns[i]);
        if (pos != -1) {
            printf("找到模式串 '%s' 在位置 %d\n", patterns[i], pos);
            return pos;
        }
    }
    return -1;
}

算法特点

核心思想: 在母串的每个可能位置尝试匹配整个子串,一旦发现不匹配就移动到下一个位置重新开始。

关键技术: 1. 双重循环:外层遍历母串起始位置,内层进行字符匹配 2. 提前终止:发现不匹配立即跳出内层循环 3. 边界处理:正确处理字符串边界和空串情况

优势: - 实现简单:逻辑清晰,易于理解和实现 - 正确性高:算法简单,不容易出错 - 适用广泛:适用于各种字符集和长度的字符串

局限性: - 效率较低:最坏情况下时间复杂度为O(m×n) - 重复比较:可能对同一字符进行多次比较

应用场景: - 文本编辑器的查找功能 - 搜索引擎的关键词匹配 - 生物信息学中的序列匹配 - 病毒扫描中的模式匹配 - 数据验证和过滤

算法改进方向: 1. KMP算法:避免重复比较,时间复杂度O(m+n) 2. Boyer-Moore算法:从右向左匹配,平均性能更好 3. Sunday算法:基于启发式规则跳过更多字符

朴素字符串匹配算法虽然效率不是最高,但因其简单性和可靠性,在实际应用中仍然广泛使用,特别是在子串较短或对性能要求不高的场景中。