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

C语言学习笔记20260628:字符串子串查找的三种解法

C语言学习笔记20260628:字符串子串查找的三种解法

一、学习目标

通过经典的“子串查找”问题,掌握字符串匹配从“暴力模拟”到“工程调用”再到“算法优化”的三种核心范式。深入理解暴力匹配法的底层逻辑,学习标准库函数strstr的指针运算技巧,并探究 KMP 算法如何通过“跳转指南(next数组)”避免主串回溯,体会算法思维在性能优化中的巨大作用。

二、问题拆解与核心逻辑

本题要求在长度为 N 的主字符串str中,查找长度为 M 的子串sub首次出现的起始下标。例如str = "abcabcd",sub = "abcd",应返回下标 3。核心约束条件为:

  1. 边界处理:需处理子串为空、子串长度大于主串等异常情况。
  2. 匹配效率:在大数据量或长文本搜索场景下,暴力法可能面临严重的性能瓶颈。

三、方法一:暴力匹配法(双重循环模拟)

3.1 核心思路

采用双重循环的暴力匹配算法。外层循环遍历主串的每个可能起始位置i,内层循环从当前起始位置开始,逐个字符与子串进行比对。一旦发现字符不匹配,主串指针i回退到本次匹配的下一个位置,子串指针j归零,重新开始比对。

3.2 完整代码实现

#define_CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<string.h>// 查找子串,返回起始下标,无则返回-1intfindSubStr(constcharstr[],constcharsub[]){intlenStr=strlen(str);intlenSub=strlen(sub);// 子串为空 或 子串比主串长,直接不存在if(lenSub==0||lenSub>lenStr)return-1;// i:主串匹配起始位置,最多走到 lenStr-lenSubfor(inti=0;i<=lenStr-lenSub;i++){intj=0;// 逐个字符比对子串while(str[i+j]==sub[j]){j++;// 子串全部匹配完成,返回起始下标iif(j==lenSub)returni;}}// 遍历完无匹配return-1;}intmain(){charmainStr[]="hello world hello c";charsub1[]="hello";charsub2[]="java";charsub3[]="c";intpos1=findSubStr(mainStr,sub1);intpos2=findSubStr(mainStr,sub2);intpos3=findSubStr(mainStr,sub3);printf("子串\"%s\"位置:%d\n",sub1,pos1);// 0printf("子串\"%s\"位置:%d\n",sub2,pos2);// -1printf("子串\"%s\"位置:%d\n",sub3,pos3);// 16return0;}

3.3 方法优缺点分析

  • 优点:逻辑极其直观,代码编写简单,直观。
  • 缺点:时间复杂度为 O(N × M)。当主串和子串都很长,且存在大量“部分匹配”的情况时(例如主串 “aaaaaab”,子串 “aaab”),效率极低。

四、方法二:调用库函数 strstr(工程最简写法)

4.1 核心思想

在实际工程项目中,无需重复造轮子。C语言标准库<string.h>提供了strstr函数,其内部通常经过了高度优化,能直接定位子串。

4.2 完整代码实现

#define_CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<string.h>// 常用封装函数(将指针结果转换为下标)intfindSubStr_lib(constcharstr[],constcharsub[]){char*p=strstr(str,sub);if(p==NULL)return-1;returnp-str;// 指针相减得到下标}intmain(){charstr[]="abcdefabc123";charsub[]="abc123";char*res=strstr(str,sub);if(res==NULL){printf("未找到子串\n");}else{// 核心技巧:指针相减得到下标intidx=res-str;printf("子串起始下标:%d\n",idx);// 6}return0;}

4.3 核心细节解析

  • 返回值strstr返回的是指向子串首次出现位置的指针,如果未找到则返回NULL
  • 指针运算:利用 C 语言指针运算的特性,res - str可以直接得到子串在主串中的偏移量(即下标)。这是指针运算的经典应用场景。

五、方法三:KMP 算法(高效匹配)

5.1 核心思想:拒绝回溯,智能跳转

KMP 算法的核心在于当发生匹配失败时,主串指针i不回溯,而是利用模式串(子串)自身的“最长公共前后缀”信息,将子串指针j智能跳转到合适的位置继续比对。这个跳转信息被存储在next数组中。

5.2 完整代码实现

#define_CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<string.h>// 生成next数组(跳转指南)voidgetNext(constcharsub[],intnext[]){intlen=strlen(sub);next[0]=-1;// 初始状态inti=0,j=-1;while(i<len-1){// j == -1 表示从头开始,或者当前字符匹配成功if(j==-1||sub[i]==sub[j]){i++;j++;next[i]=j;// j的值就是当前子串的最长公共前后缀长度}elsej=next[j];// 匹配失败,回溯j,寻找更短的公共前后缀}}// KMP查找子串intkmpFind(constcharstr[],constcharsub[]){intlenS=strlen(str);intlenT=strlen(sub);if(lenT==0||lenT>lenS)return-1;intnext[100]={0};// 实际工程中应动态分配或使用模式串长度getNext(sub,next);inti=0,j=0;// i为主串指针,j为模式串指针while(i<lenS&&j<lenT){// j == -1 表示模式串从头开始比对,或者当前字符匹配成功if(j==-1||str[i]==sub[j]){i++;j++;}elsej=next[j];// 匹配失败,根据next数组调整模式串指针}// j 走到模式串末尾,说明匹配成功if(j==lenT)returni-j;// 返回主串中匹配开始的下标return-1;}intmain(){chars[]="ababcabcabx";chart[]="abcabx";printf("KMP匹配下标:%d",kmpFind(s,t));// 5return0;}

5.3 核心细节解析

  • next数组的推导next[i]存储的是子串前i个字符组成的子串中,最长相等前后缀的长度。当sub[i] != sub[j]时,通过j = next[j]不断回溯,直到找到匹配或j == -1
  • 主串指针不回溯:这是 KMP 算法时间复杂度能达到 O(N + M) 的根本原因。无论子串多长,主串指针i始终只增不减。

六、总结与工程实践建议

对比维度暴力匹配法库函数 strstrKMP 算法
核心逻辑双重循环、失败回溯调用标准库、指针运算next数组、主串不回溯
时间复杂度O(N × M)取决于库实现(通常优化较好)O(N + M)
代码复杂度极低
适用场景数据量小、理解原理日常工程开发算法竞赛、海量文本搜索

总结
暴力匹配法是理解字符串底层操作的基石;strstr库函数展示了标准库在提升开发效率上的巨大优势;而 KMP 算法则体现了计算机科学中“用空间(next数组)换时间”以及“利用已知信息避免重复计算”的极致优化思想。在实际开发中,日常业务优先使用库函数;若遇到极端的性能瓶颈或算法竞赛,KMP 则是解决长字符串匹配问题的终极武器。

http://www.jsqmd.com/news/1086953/

相关文章:

  • 3步搞定HS2-HF Patch安装:解锁HoneySelect2完整游戏体验的终极指南
  • Playnite游戏库管理器:跨平台游戏统一管理的终极解决方案
  • BetterNCM安装器:让你的网易云音乐秒变智能播放器
  • 3分钟免费AI视频生成:零基础打造专业数字内容
  • SHA-3/SHAKE统一架构设计与容错优化
  • 抖音无水印下载终极指南:5步轻松获取高清视频的完整教程
  • CookieCloud与Playwright集成:实现自动化测试登录态持久化
  • MagicSkin触觉传感器:半透明标记设计实现高精度力与纹理感知
  • BetterNCM安装器终极指南:5分钟解锁网易云音乐无限功能
  • 5分钟搞定QQ音乐加密文件:qmcdump让音乐播放不再受限
  • Java毕设选题推荐:面向同城用户的在线房屋租赁平台的设计与实现 基于 Web 的智能化房源筛选租房系统【附源码、mysql、文档、调试+代码讲解+全bao等】
  • 55.TIA V17 实测通过|带故障锁定、手自动切换、报警闪烁 PLC 工程
  • Hive实战演练:从电影评分数据中挖掘用户行为洞察
  • HS2-HF_Patch:三分钟解锁《Honey Select 2》完整汉化与优化体验
  • 告别皮肤权重噩梦:如何用brSmoothWeights让Maya角色动画效率提升300%
  • 终极植物大战僵尸修改器:如何用PVZ Toolkit彻底改变你的游戏体验
  • GSE-Advanced-Macro-Compiler:终极魔兽世界技能自动化工具完整指南
  • XGP存档提取终极指南:3步轻松迁移Xbox游戏存档到Steam
  • openEuler ubutils与内核模块交互:ubfi.ko与ubus.ko加载指南
  • WebPageTest深度指南:从核心原理到私有化部署的性能优化实战
  • 【精通】RustMark v2.3:测试体系 — Rust 单元/集成/文档/Fuzz 测试实战
  • 3分钟快速上手:终极免费在线EPUB编辑器完整指南
  • Linux 系统下 Anaconda 的安装与配置实战
  • 从装箱问题到01背包:动态规划在NOIP经典题目中的实战解析
  • 惠普暗影精灵笔记本性能优化终极指南:OmenSuperHub完全使用教程
  • OpCore Simplify:终极OpenCore EFI自动化配置工具完全指南
  • Xournal++插件开发实战:从零构建自定义快捷键
  • 揭秘Upscayl:开源AI图像超分辨率技术的深度解析与实战指南
  • Universal Pokemon Randomizer ZX:终极宝可梦随机化工具完全指南 [特殊字符]
  • 缠论量化工程化:从理论到实战的Python实现框架