【数据结构和算法】字符串遍历-KMP算法

简介: 【数据结构和算法】字符串遍历-KMP算法

1、KMP算法的介绍


前提:

 BF的算法效率是比较低下的,KMP算法是字符串查找遍历的另一种小乱比较高的算法。KMP算法的核心就是避免不必要的回溯,问题有模式串决定,不是有目标决定。

以下是几个思路启发,对KMP算法进行独自的思考:


思路启发一:

image.png

对于这个例子,当出现失配的情况之前,前面的内容子串和母串都是一一匹配的,而且子串的内容各不相同,所以当出现失配的情况时,不需要回溯到 L 浪费效率,而是应该从 e 重新开始匹配。

image.png


思路启发二:

image.png

这种情况下,由于出现了相同的字符,也就子串失配的位置前有前缀和后缀是一样的,所以不适用于思路启发一。而是应该在第二个w中进行匹配。也就是子串的第一个字符j[1]应该与母串的i[2]进行匹配。如下所示:

image.png


思路启发三:

image.png

思路启发三是根据思路启发二进行修改,应该这种子串的重复形式与思路启发二不同。此处的前缀和后缀分别有两个字符串表示。也就是这种情况下的j[1]和j[2]与i[4]i[5]是匹配的,而且与j[4]j[5]也是相同的,所以j[1]应该放在i[4]处开始匹配,不能放过任何一种的匹配可能,如下所示:

image.png


思路启发四

image.png

这种情况是思路启发二的极端情况,应该按如下的匹配方式

image.png

以上是四条规律总结出来就是KMP算法。


2、接下来对next数组进行探讨:

 next数组,指导着模式匹配串下一步改用第几号元素进行去匹配。

(0号元素分别存储着子串和母串的元素多少)

再次补充:

KMP的next数组的作用是指导T这个模式匹配串下一次匹配应该匹配的位置。

以思路启发一为例:

image.png

由于思路启发一的子串个元素个不相同,所以如果当任意位置失配的时候,都是以j[1]来进行重新的配对。但是如果是第一个就失配的话,已经不可能拿第一个再去和母串进行匹配,而是应该拿0号元素。而这里的0号元素存放着整个字符串的长度。所以,这时候可以用一个while来进行判断,如果当j == 0时,i++,j++,这时候就同时移位再次判断。next数组和移位后如下所示:


以思路启发二为例:

此时子串i[2]的前缀和后缀是相同的,所以j[3]失配的时候,此位应该是由j[2]来进行重新匹配。思路启发二的next数组如下所示:

image.png


以思路启发三为例:

当j[6]出现失配的时候,由于j[6]的前缀bb和后缀bb相同,所以有两个元素相同,对应的next数组应该为3,也就是此位应该由j[3]进行重新的匹配。

而当j[5]出现失配的时候,由于前缀和后缀只有一个元素相同,所以对应的next数组应该为2,也就是此位应该由j[2]来进行重新匹配。

其他的类似

所以,思考启发三的next数组如下所示;

image.png


以思路启发四为例:

由上诉可知,当j[5]发生失配的时候,前缀和后缀分别有3个元素对应,所以对应的next数组值应该是4。其他的类似计算,思考启发四的next数组如下所示:

image.png


补充:前缀和后缀的判断

前缀和后缀是以失配的位置来进行判断的,前缀和后缀是是相对的,而子串的第一个位置一定是前缀。而后缀是位置的不同相对判断的。

下面一子串 ababaaaba为例,首元素是j[1]= a,对于j[6]元素来说,其前缀和后缀如下所示:

image.png


前缀和后缀如下所示:

image.png


可见,最高其前缀和后缀的最高相同元素是3个,所以其对应的next数组值是4。

image.png


3、next数组的实现

 当模式匹配串T失配的时候,NEXT数组对应的元素知道应该用T串的那个元素进行下一轮的匹配。

image.png

next数组获取代码如下:

void  GetNext(char *son, int *next)
{
  int front = 0;  //前缀
  int back = 1;   //后缀
  next[1] = 0;    //next数组的第一个元素肯定为0
  while (back < strlen(son))  //当后缀未到达最后一个字符时
  {
  if (0 == front || son[back] == son[front])  //当匹配时,应该都同时再加1进行下一个数的匹配
  {
    back++;
    front++;
    next[back] = front;
  }
  else  //当失配时回溯寻找前缀front,因为前缀是固定,而后缀是相对的
    front = next[front];
  }
}


测试代码:

int main()
{
  char Big[256] = " ababaaaba";
  int next[256] = { 0 };
  GetNext(Big, next);
  for (int i = 1; i < strlen(Big); i++)
  printf("%d ", next[i]);
  return 0;
}


结果如下:

image.png

4、KMP算法实现

代码如下:

int KMPFindString(char *mom, char *son, int pos)
{
  int next[256] = {0};
  int mompos = pos, sonpos = 1;
  GetNext(son, next);
  //以下和bf算法相似
  while (mompos <= sizeof(mom) &&  sonpos <= strlen(son) )  
  {
  if ( (sonpos == 0) || (mom[mompos] == son[sonpos]) )
  {
    mompos++;
    sonpos++;
  }
  else
    sonpos = next[sonpos];
  }
  if (sonpos > strlen(son)) //整个成功的匹配了
  return mompos - strlen(son);
  else
  return 0;
}


参考链接:https://www.bilibili.com/video/av21828275

目录
相关文章
|
16天前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》算法题基础篇—链表(下)
408考研——《数据结构》算法题基础篇之链表(下)。
79 29
|
16天前
|
存储 算法 C语言
C 408—《数据结构》算法题基础篇—链表(上)
408考研——《数据结构》算法题基础篇之链表(上)。
72 25
|
16天前
|
存储 人工智能 算法
C 408—《数据结构》算法题基础篇—数组(通俗易懂)
408考研——《数据结构》算法题基础篇之数组。(408算法题的入门)
58 23
|
1月前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
51 2
|
2月前
|
存储 运维 监控
探索局域网电脑监控软件:Python算法与数据结构的巧妙结合
在数字化时代,局域网电脑监控软件成为企业管理和IT运维的重要工具,确保数据安全和网络稳定。本文探讨其背后的关键技术——Python中的算法与数据结构,如字典用于高效存储设备信息,以及数据收集、异常检测和聚合算法提升监控效率。通过Python代码示例,展示了如何实现基本监控功能,帮助读者理解其工作原理并激发技术兴趣。
70 20
|
3月前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
91 1
|
3天前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
1月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
142 77
|
1月前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
38 7
|
1月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
43 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】

热门文章

最新文章