6-4 查找子串(20 分)

这篇博客介绍如何在C语言中实现一个简单的字符串查找函数,该函数用于在主字符串中寻找指定子串,并返回子串的起始位置。如果未找到,函数返回NULL。文章包含函数接口定义和裁判测试程序的样例。

6-4 查找子串(20 分)

本题要求实现一个字符串查找的简单函数。

函数接口定义:

char *search( char *s, char *t );

函数search在字符串s中查找子串t,返回子串t在s中的首地址。若未找到,则返回NULL。

裁判测试程序样例:

#include <stdio.h>
#define MAXS 30

char *search(char *s, char *t);
void ReadString( char s[] ); /* 裁判提供,细节不表 */

int main()
{
    char s[MAXS], t[MAXS], *pos;

    ReadString(s);
    ReadString(t);
    pos = search(s, t);
    if ( pos != NULL )
   
<think>题目要求实现一个字符串查找函数,返回子串t在s中首次出现的地址,若未找到则返回NULL。 我们可以使用暴力匹配算法(Brute Force)或者更高效的算法(如KMP),但考虑到简单性和题目要求,这里使用暴力匹配即可。 暴力匹配的思路:遍历字符串s的每个字符作为起始点,然后与t进行逐字符匹配,如果完全匹配则返回当前起始地址;如果遍历完s都没有找到,则返回NULL。 具体步骤: 1. 如果t是空字符串,根据约定,我们返回s(因为空串是任何字符串的子串,但题目没有明确,但通常返回s的起始位置)。但题目中裁判程序输入的是两个字符串,一般不会有空串,但为了安全,我们可以考虑:如果t为空,则返回s。 2. 遍历s中的每个字符,从当前位置开始,将s的当前字符与t的字符逐一比较。 3. 如果t的所有字符都匹配了,则返回当前s中的起始位置。 4. 如果中途有字符不匹配,则从s的下一个字符开始重新比较。 5. 如果直到s结束都没有找到,则返回NULL。 注意:在遍历s时,我们可以限制遍历范围,因为当s剩余的长度小于t的长度时,肯定不匹配,可以提前结束。 实现代码: char *search(char *s, char *t) { if (t[0] == '\0') // 如果t是空串,返回s(题目可能不会出现,但安全起见) return s; int i, j; for (i = 0; s[i] != '\0'; i++) { int k = i; j = 0; // 在s[i]开始的子串与t比较 while (s[k] != '\0' && t[j] != '\0' && s[k] == t[j]) { k++; j++; } // 如果t被完全匹配了,即t[j]=='\0' if (t[j] == '\0') return s + i; // 返回从位置i开始的地址 } return NULL; // 未找到 } 但是,上面的代码在每次匹配失败后,i会递增,然后重新从t的开头开始匹配。这是一种暴力匹配,时间复杂度为O(n*m),其中n和m别是s和t的长度。 另一种写法(使用指针): char *search(char *s, char *t) { if (*t == '\0') return s; // 空子串返回s的首地址 char *p, *q, *cur; for (cur = s; *cur != '\0'; cur++) { p = cur; q = t; while (*p && *q && *p == *q) { p++; q++; } if (*q == '\0') // 匹配到t的结尾,说明找到了 return cur; } return NULL; } 我们选择指针版本,因为它更符合C语言的风格。 但是,我们还需要考虑效率问题。如果字符串很长,暴力匹配效率较低,但题目没有要求效率,且字符串最大长度为30(由MAXS定义),所以暴力匹配足够。 因此,我们采用指针版本。 测试用例: 输入样例1:s="The C Programming Language", t="ram" -> 返回指向s中第10个字符(从0开始计数)的地址,即输出10(因为pos-s=10)。 输入样例2:s="The C Programming Language", t="bored" -> 返回NULL,输出-1。 注意:字符串中空格也是字符。 让我们手动计算样例1:字符串s中,第一个字符'T'是0,然后依次...,子串"ram"出现在哪里? 原字符串:The C Programming Language 索引位置:0:T, 1:h, 2:e, 3:空格, 4:C, 5:空格, 6:P, 7:r, 8:o, 9:g, 10:r, 11:a, 12:m, ... 注意,题目要求返回的是子串首地址,对于"ram"来说,首字符'r'在s中的位置是索引10(因为从0开始数,第一个字符是0,第二个是1,...,第11个字符是索引10)。 为什么是10?因为"Programming"中的"ram"是从第10个索引(从0开始)开始的: P(6), r(7), o(8), g(9), r(10), a(11), m(12), ... 但是注意,样例输出是10,而子串"ram"中的第一个字符'r'确实在位置10(因为上面列出的第7个字符是'r',但那是第一个'r',而我们要找的是"ram",所以是第二个'r',在索引10处)。 然而,在字符串中查找子串"ram": 第一次出现:在"Programming"中,从索引10开始(即第二个'r'开始)的"ram":s[10]='r', s[11]='a', s[12]='m'。 所以返回的地址是s+10,然后主函数中输出pos-s,即10,符合样例。 因此,代码正确。 下面给出最终代码: </think> ### 实现字符串查找子串函数 以下是符合题目要求的 `search` 函数实现: ```c char *search(char *s, char *t) { // 处理空子串的特殊情况 if (*t == '\0') return s; char *p; while (*s != '\0') { p = t; // 每次从子串开头开始匹配 char *cur = s; // 记录当前匹配起始位置 // 逐个字符比较 while (*cur != '\0' && *p != '\0' && *cur == *p) { cur++; p++; } // 如果子串全部匹配完成 if (*p == '\0') { return s; // 返回当前起始地址 } s++; // 移动到下一个起始位置 } return NULL; // 未找到子串 } ``` ### 实现原理和关键点解释 1. **空子串处理**: - 如果子串 `t` 是空字符串(`*t == '\0'`),函数返回 `s` 的起始地址 - 根据C语言惯例,空串被视为所有字符串的子串 2. **双重循环匹配**: - **外层循环**:遍历源字符串 `s` 的每个字符作为匹配起点 - **内层循环**:从当前位置开始,逐个字符比较 `s` 和 `t` 的内容 - **cur指针**:记录当前匹配位置,避免修改外层循环的 `s` 指针 3. **匹配成功条件**: - 当 `*p == '\0'` 时,说明子串 `t` 的所有字符都已匹配 - 此时返回当前起始地址 `s` 4. **未找到处理**: - 当外层循环结束仍未找到匹配,返回 `NULL` 5. **时间复杂度**: - 最坏情况:O(n*m)(n和m别是s和t的长度) - 最佳情况:O(1)子串在开头匹配) ### 测试用例验证 - **样例1**: `s = "The C Programming Language", t = "ram"` 返回地址 `s + 10` → 输出 `10` - **样例2**: `s = "The C Programming Language", t = "bored"` 返回 `NULL` → 输出 `-1` - **边界测试**: `s = "abc", t = ""` → 返回 `s`(地址差为0) `s = "abc", t = "abcd"` → 返回 `NULL` `s = "aaa", t = "aa"` → 返回 `s`(首个匹配位置) ### 常见错误析 1. **未处理空子串**:导致无限循环或错误结果 2. **指针修改错误**:内层循环修改了外层循环的 `s` 指针 3. **边界条件缺失**:未考虑源字符串提前结束的情况 4. **返回值错误**:返回匹配结束位置而非起始位置 --- ###
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值