news 2026/6/15 14:15:51

数据结构 | 两大算法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
数据结构 | 两大算法

一个人能走多远不在于他在顺境时能走得多快,而在于他在逆境时多久能找到曾经的自己。

—— KMP

字符串匹配作为计算机领域最核心的基础算法之一,广泛应用于文本检索、编译器词法分析、网络数据解析、病毒特征匹配等各类实际开发场景中。高效的字符串匹配算法能够大幅提升程序运行效率,降低时间损耗,而 KMP 算法正是解决字符串匹配问题的经典高效算法,它彻底解决了暴力匹配算法的效率缺陷。


一、字符串匹配问题

在程序开发中,字符串匹配是最常见的需求之一:给定一个主串(目标字符串)和一个子串(模式字符串),我们需要完成的核心任务是,判断子串是否作为一个连续片段存在于主串当中。 如果存在,返回子串在主串中第一次出现的起始下标;如果不存在,则返回 - 1 作为匹配失败的标识。 这个问题看似简单,却是整个字符串处理领域的基础,而解决这个问题的算法,从最朴素的暴力枚举,到高效的 KMP 算法,体现了算法优化的核心思想。


二、BF 算法(暴力枚举法)

2.1 BF 算法描述

BF 算法全称 Brute Force,即暴力匹配算法,是字符串匹配问题中最直观、最容易理解的解法,它不依赖任何复杂的预处理逻辑,完全依靠逐字符比对完成匹配。 算法执行流程:

  • 定义两个下标变量iji用于遍历主串,初始指向主串起始位置,j用于遍历子串,初始指向子串起始位置
  • 进入循环匹配,循环的核心条件是ij均未超出对应字符串的有效长度,保证比对的字符是合法有效的
  • 若当前ij指向的字符相等,两个指针同步向后移动一位,继续比对下一个字符
  • 若当前字符不相等,匹配失败,i回退到本趟匹配起始位置的下一个字符,j直接回退到子串起始位置 0,重新开始新一轮匹配
  • 循环结束后,通过j的值判断匹配结果:若j遍历完整个子串,说明匹配成功,返回起始下标;否则匹配失败,返回 - 1

BF 算法的核心缺陷十分明显:每次匹配失败后,主串指针i需要大量回退,造成了大量重复的字符比对操作,在数据量较大时,算法效率极低。时间复杂度:最坏情况下为O(n*m)(n 为主串长度,m 为子串长度)。

2.2 BF 算法实现

// BF暴力匹配算法实现 int BF_Search(const char* des, const char* sub) { // 断言校验,确保传入的字符串指针非空,避免空指针访问 assert(des != nullptr); assert(sub != nullptr); int i = 0, j = 0; int des_len = strlen(des); int sub_len = strlen(sub); // 循环条件:主串和子串指针均未越界 while (i < des_len && j < sub_len) { if (des[i] == sub[j]) { // 字符匹配,两个指针同时后移 i++; j++; } else { // 匹配失败,i回退,j重置为0 i = i - j + 1; j = 0; } } // j遍历完子串,说明匹配成功 if (j == sub_len) return i - j; else // 匹配失败 return -1; }

三、KMP 算法

3.1 KMP 算法思想

KMP 算法是对 BF 暴力算法的颠覆性优化,它的核心突破点在于:主串指针i永远不回退,仅通过调整子串指针j的位置完成匹配,彻底消除了重复比对的操作,将算法时间复杂度优化到O(n+m),在长文本匹配场景中优势极其显著。

KMP 算法的核心依托是next 数组,这个数组是针对子串预处理得到的,存储了子串中每个字符失配时,j应该回退的最优下标,无需让i回退重新匹配,直接跳过不可能成功的匹配位置。

3.2 为什么主串指针i可以不回退?

当匹配发生失败时,我们只需要关注子串失配位置之前的字符片段:

  • 若该片段中存在最长相等的前缀和后缀(左橙 = 右橙),i的回退没有任何实际意义,这种回退必然会导致匹配失败,完全可以通过j跳转到 next 数组指定的位置替代
  • 若该片段中不存在相等的前缀和后缀,那么i无论如何回退,都无法完成匹配,因此i更没有回退的必要
  • 综上,无论子串失配位置前是否存在相等前后缀,主串指针i都可以保持不回退,这是 KMP 算法高效的核心逻辑。

3.3 KMP 算法实现

// KMP匹配算法实现 int KMP_Search(const char* des, const char* sub); // 获取next数组 int* Get_Next(const char* sub); int main() { const char arr[] = "aaabccc"; const char brr[] = "abc"; // 调用KMP算法进行匹配 int res = KMP_Search(arr, brr); if (res == -1) cout << "匹配失败,未找到目标子串" << endl; else cout << "匹配成功,子串起始下标为:" << res << endl; return 0; } int KMP_Search(const char* des, const char* sub) { // 合法性校验 assert(des != nullptr); assert(sub != nullptr); int i = 0, j = 0; int des_len = strlen(des); int sub_len = strlen(sub); // 为子串生成next数组,仅需计算一次 int* next = Get_Next(sub); // 主循环匹配,i不回退 while (i < des_len && j < sub_len) { // j=-1表示退无可退,或字符匹配成功,指针后移 if (j == -1 || des[i] == sub[j]) { i++; j++; } else { // 匹配失败,j根据next数组回退到最优位置 j = next[j]; } } // 释放动态申请的next数组内存,防止内存泄漏 free(next); // 判断匹配结果 if (j == sub_len) return i - j; else return -1; }

四、Next 数组

4.1 Next 数组定义

Next 数组是专门针对子串预处理生成的辅助数组,具备两个特征:

  • 数组长度与子串的字符个数完全一致,一一对应子串的每一个位置
  • 数组中存储的数值,代表子串在当前下标位置发生失配时,j指针需要回退的最优下标位置

Next 数组的本质,是寻找子串中每个位置最长相等前缀和后缀的长度,这个长度决定了j的回退位置,能够最大程度减少无效匹配。

4.2 Next 数组求解

第一种:直接寻找最长相等前后缀(左橙 = 右橙),前缀长度即为对应位置的 next 值。

第二种:用已知求未知,这是代码实现的核心方法:利用已经计算完成的 next 值,推导下一个位置的 next 值。 核心规则:如果当前字符与回退位置的字符相同,说明前后缀可以延长一位,next 值 + 1;如果不同,则继续回退,直到找到匹配位置或退至 - 1。

4.3 Next 数组实现

// 生成子串对应的next数组 int* Get_Next(const char* sub) { // 校验子串合法性 assert(sub != nullptr); int len = strlen(sub); // 动态分配内存,存储next数组 int* Next = (int*)malloc(len * sizeof(int)); // 内存分配失败,直接退出程序 if (Next == nullptr) exit(EXIT_FAILURE); // 手动初始化前两个位置的next值,固定规则 Next[0] = -1; if (len > 1) Next[1] = 0; int j = 1; int k = 0; // 循环计算剩余位置的next值 while (j + 1 < len) { // 回退到起点,或字符匹配,next值+1 if (k == -1 || sub[k] == sub[j]) { Next[++j] = ++k; } else { // 不匹配,继续回退k k = Next[k]; } } return Next; }

五、Nextval 数组

Nextval 数组是对 Next 数组的进阶优化,它解决了原始 Next 数组中存在的无意义回退问题,进一步减少 KMP 算法的匹配次数,让算法效率更高。

  • 如果当前位置的字符,与它 Next 数组指向的回退位置字符不相同,说明本次回退是有价值的,直接将当前位置的 Next 值赋值给 Nextval
  • 如果当前位置的字符,与它 Next 数组指向的回退位置字符相同,说明本次回退没有意义,匹配依然会失败,直接将回退位置的 Nextval 值赋值给当前位置

简单来说,Nextval 数组会跳过所有重复的、无意义的回退操作,让j指针一步到位跳转到最终的有效位置,避免了多余的字符比对,是 KMP 算法的终极优化形态。

六、算法总结

BF 算法 vs KMP 算法

Next 数组 vs Nextval 数组

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/15 14:12:02

C语言系统编程核心:stat函数、可变参数与跨平台文件处理实战

1. 项目概述&#xff1a;深入C标准库的实用核心在C语言的世界里&#xff0c;标准库函数就像是程序员的瑞士军刀。无论你是想读写一个配置文件&#xff0c;还是需要处理用户输入的不定数量参数&#xff0c;又或者是在不同操作系统间小心翼翼地处理文件路径&#xff0c;最终都绕不…

作者头像 李华
网站建设 2026/6/15 14:11:31

如何彻底告别网盘限速:九大主流网盘直链解析工具完整指南

如何彻底告别网盘限速&#xff1a;九大主流网盘直链解析工具完整指南 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里云盘 / 中国移动云盘 / 天…

作者头像 李华
网站建设 2026/6/15 14:08:47

MPC866内存同步与异常处理:嵌入式系统稳定性的核心机制

1. MPC866内存同步与异常处理&#xff1a;嵌入式系统稳定性的基石在嵌入式系统开发&#xff0c;尤其是涉及实时控制、多任务调度或外设通信的场景里&#xff0c;有两个底层机制是开发者必须深刻理解并妥善运用的&#xff1a;内存同步与异常处理。它们不像应用层算法那样直观&am…

作者头像 李华
网站建设 2026/6/15 14:05:52

d3d8to9:让经典Direct3D 8游戏在现代Windows系统上重生

d3d8to9&#xff1a;让经典Direct3D 8游戏在现代Windows系统上重生 【免费下载链接】d3d8to9 A D3D8 pseudo-driver which converts API calls and bytecode shaders to equivalent D3D9 ones. 项目地址: https://gitcode.com/gh_mirrors/d3/d3d8to9 你是否曾经遇到过这…

作者头像 李华
网站建设 2026/6/15 14:02:54

深入解析MPC8533E:PowerQUICC III核心寄存器配置与底层驱动实战

1. 项目概述如果你在嵌入式网络设备、工业控制或者通信网关领域摸爬滚打过几年&#xff0c;大概率会跟飞思卡尔&#xff08;现恩智浦&#xff09;的PowerQUICC系列处理器打过交道。这个系列可以说是通信处理器的“常青树”&#xff0c;从早期的PowerQUICC I到后来的PowerQUICC …

作者头像 李华
网站建设 2026/6/15 14:01:52

博客数据验真器:用AI识别SEO指标中的幽灵展示与卡顿停留

1. 项目概述&#xff1a;当博客数据开始“说谎”&#xff0c;我用AI做了个真相探测器“我建了一个AI工具&#xff0c;来揭穿我的博客正在讲述的谎言”——这个标题不是修辞&#xff0c;不是营销噱头&#xff0c;也不是对算法偏见的泛泛而谈。它描述的是一个真实发生在我自己运营…

作者头像 李华