数据结构:KMP算法的原理图解和代码解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 数据结构:KMP算法的原理图解和代码解析

本篇总结的是关于串中的KMP算法解析

应用场景

现给定两个串,现在要看较短的一个串是不是较长的串的子串,如果是就输出子串后面的内容,如果不是则输出Not Found

能匹配到:

长串:qwertabcde

短串:abcd

则可以在长串中找到短串的内容,则输出abcde

匹配不到:

长串:qwertabcde

短串:afcd

则无法在长串中匹配到短串的内容,则输出Not Found

算法方案

对于如何匹配串的问题,首先是一种暴力的方案,例如让短串的内容不断地和长串进行匹配,如果在短串和长串中对应到了,就两个同时向后移动,如果短串到头,就说明匹配成功了,如果遇到其他字符,就重新进行匹配,这就是暴力求解的方案,但是时间复杂度高,总体来说是一个O(MN)的时间复杂度

这样的时间复杂度对于算法来说是比较高的,于是有三个大佬KnuthMorrisPratt,发明了一个著名的字符串匹配算法,因此这个算法的名字就被命名为KMP算法

算法原理

为了方便叙述,定义str是这里的长串,pattern是要匹配的串

算法原理就是创建一个next数组,里面存储的是pattern中,下标为i的字符前的字符串最长相等前后缀的长度

那什么是最长相等前后缀?用下面的例子来举例:

假设现在patternabcab,那么对于pattern来说,它的前后缀分别有:

前缀:{a,ab,abc,abca,abcab}

后缀:{b,ab,cab,bcab,abcab}

因此对于pattern来说,它的next数组可以这么表示

pattern的最后一个字符来看,它的前面的字符串是abca,而对于这个串来说的相等的前后缀只有a这一个,因此这里填入的就是a的长度也就是1

但是这个数组有什么用?从下面这个例子来看:

假设现在strabcabeabcabcmnpatternabcabcmn

那么写出patternnext数组:

下面就开始进行匹配了,当匹配到ec的时候匹配失败了,此时如果是暴力算法的思路来看,需要让patternstr的第二个字符开始对齐,再重新匹配,但是对于KMP算法来说,next数组的作用就出现了

只需要让不匹配的字符下标对应的next下标的值,回溯到pattern下标即可

以上面的例子为例,现在是s[5]p[5]的匹配失败了,那么next[5]对应的数据是2,那么就意味着现在要让s[5]p[2]进行对齐匹配,也就是说,设匹配失败的字符下标为i,那么就要让s[i]p[next[i]]进行匹配

这样就是一个循环了,进行多次循环即可,这也就是KMP算法的核心所在

next数组的意义:

  1. 下标为i的字符前的字符串最长相等前后缀的长度
  2. 该处字符不匹配时应该回溯到的字符的下标

上面的next数组写法只是手算出来的,在实际算法中需要将next数组用代码实现写出来:

void GetNext(const string& pattern, vector<int>& next)
{
  int i = 0, j = -1;
  next[0] = -1;
  while (i < pattern.size() - 1)
  {
    if (j == -1 || pattern[i] == pattern[j])
    {
      next[++i] = ++j;
    }
    else
    {
      j = next[j];
    }
  }
}

对于上面的代码来进行解析:

  1. 如果两个i和j的对应的字符相等,那么i和j就同步向后移动
  2. 如果不相等,就要进行回退了,回退到它们原来最长公共前后缀的地方,i指向的是后面的最长公共前后缀,j回退到前面的最长公共前后缀,如果这两个前后缀相等,那么这就组成了一个新的最长相等前后缀,就可以进行数据的写入了

关于求出next数组后,利用这个数组求KMP算法的代码:

int KMP(const string& str, const string& pattern, const vector<int>& next)
{
  int i = 0, j = 0;
  while (i < (int)str.size() && j < (int)pattern.size())
  {
    if (j == -1 || str[i] == pattern[j])
    {
      i++, j++;
    }
    else
    {
      j = next[j];
    }
  }
  if (j == pattern.size())
  {
    return i - j;
  }
  else
  {
    return -1;
  }
}

在知道next数组后,解决剩下的问题就很容易了,只需要一一进行比对,如果不满足条件就进行回溯,如果走到头就返回下标,如果不满足条件就返回-1

完整代码

#include <bits/stdc++.h>
using namespace std;
// KMP算法,给定两个字符串,用子串去匹配长字符串,如果匹配成功就输出匹配的字符串和后面的内容
// 如果匹配不成功就输出NOT FOUND
void GetNext(const string& pattern, vector<int>& next)
{
  int i = 0, j = -1;
  next[i] = j;
  while (i < pattern.size() - 1)
  {
    if (j == -1 || pattern[i] == pattern[j])
    {
      next[++i] = ++j;
    }
    else
    {
      j = next[j];
    }
  }
}
int KMP(const string& str, const string& pattern, const vector<int>& next)
{
  int i = 0, j = 0;
  while (i < (int)str.size() && j < (int)pattern.size())
  {
    if (j == -1 || str[i] == pattern[j])
    {
      i++, j++;
    }
    else
    {
      j = next[j];
    }
  }
  if (j == pattern.size())
  {
    return i - j;
  }
  else
  {
    return -1;
  }
}
void PrintString(const string& str, int index)
{
  string res;
  for (int i = index; i < str.size(); i++)
  {
    res += str[i];
  }
  cout << res << endl;
}
int main()
{
  // str是长字符串,pattern是要匹配的子串
  string str, pattern;
  cin >> str >> pattern;
  // KMP算法首先计算出pattern的next数组
  vector<int> next(pattern.size());
  GetNext(pattern, next);
  // 根据str,pattern,next数组进行匹配
  int index = KMP(str, pattern, next);
  // 得出结果
  if (index == -1)
  {
    cout << "NOT FOUND" << endl;
  }
  else
  {
    PrintString(str, index);
  }
  return 0;
}


相关文章
|
1月前
|
搜索推荐 UED Python
实现一个带有昼夜背景切换的动态时钟:从代码到功能解析
本文介绍了一个使用Python和Tkinter库实现的动态时钟程序,具有昼夜背景切换、指针颜色随机变化及整点和半点报时功能。通过设置不同的背景颜色和随机变换指针颜色,增强视觉吸引力;利用多线程技术确保音频播放不影响主程序运行。该程序结合了Tkinter、Pygame、Pytz等库,提供了一个美观且实用的时间显示工具。欢迎点赞、关注、转发、收藏!
134 94
|
3月前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
91 1
|
15天前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》算法题基础篇—链表(下)
408考研——《数据结构》算法题基础篇之链表(下)。
78 29
|
15天前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》图、查找、排序专题考点(含解析)
408考研——《数据结构》图,查找和排序专题考点选择题汇总(含解析)。
67 29
|
15天前
|
存储 机器学习/深度学习 人工智能
C 408—《数据结构》易错考点200题(含解析)
408考研——《数据结构》精选易错考点200题(含解析)。
90 27
|
15天前
|
存储 算法 C语言
C 408—《数据结构》算法题基础篇—链表(上)
408考研——《数据结构》算法题基础篇之链表(上)。
72 25
|
15天前
|
存储 人工智能 算法
C 408—《数据结构》算法题基础篇—数组(通俗易懂)
408考研——《数据结构》算法题基础篇之数组。(408算法题的入门)
58 23
|
1月前
|
SQL Java 数据库连接
如何在 Java 代码中使用 JSqlParser 解析复杂的 SQL 语句?
大家好,我是 V 哥。JSqlParser 是一个用于解析 SQL 语句的 Java 库,可将 SQL 解析为 Java 对象树,支持多种 SQL 类型(如 `SELECT`、`INSERT` 等)。它适用于 SQL 分析、修改、生成和验证等场景。通过 Maven 或 Gradle 安装后,可以方便地在 Java 代码中使用。
262 11
|
2月前
|
自然语言处理 搜索推荐 数据安全/隐私保护
鸿蒙登录页面好看的样式设计-HarmonyOS应用开发实战与ArkTS代码解析【HarmonyOS 5.0(Next)】
鸿蒙登录页面设计展示了 HarmonyOS 5.0(Next)的未来美学理念,结合科技与艺术,为用户带来视觉盛宴。该页面使用 ArkTS 开发,支持个性化定制和无缝智能设备连接。代码解析涵盖了声明式 UI、状态管理、事件处理及路由导航等关键概念,帮助开发者快速上手 HarmonyOS 应用开发。通过这段代码,开发者可以了解如何构建交互式界面并实现跨设备协同工作,推动智能生态的发展。
196 10
鸿蒙登录页面好看的样式设计-HarmonyOS应用开发实战与ArkTS代码解析【HarmonyOS 5.0(Next)】
|
1月前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
51 2

热门文章

最新文章

推荐镜像

更多