查找子字符串----KMP算法深入剖析
假设主串:a b a b c a b c a c b a b
子串:a b c a c
1、一般匹配算法
逐个字符的比较,匹配过程如下:
第一趟匹配
a b a b c a b c a c b a b
a b c
第二趟
a b a b c a b c a c b a b
a
第三趟
a b a b c a b c a c b a b
a b c a c
第四趟
a b a b c a b c a c b a b
a
第五趟
a b a b c a b c a c b a b
a
第六趟
a b a b c a b c a c b a b
a b c a c
匹配成功。
性能分析:情况好:时间复杂度O(m+n);情况差:时间复杂度O(m*n)。
2、一般匹配算法改进
即KMP算法。可以发现上面的算法,每一趟匹配过程中出现字符不等时,回溯指针,如果将其改进,指针不回溯,利用已经得到的部分匹配的结果将模式向右移动的更远一些,然后继续比较。那么算法性能会得到大大的提高。
看到上面的过程,在第三趟的匹配过程中,当i=6,j=4字符不等时,又从i=3,j=0重新开始比较。其实可以容易发现,在i=3和 j=0,i=4和i=0以及i=5和j=0这3次比较都是不必进行的。因为从第三趟部分匹配结果就可以得出,主串中第3,4,5个字符是’b’,’c’,’a’。而模式中第一个字符是’a’,因此无需和这3个字符进行比较了,紧需要向右移动3个字符继续进行i=6,j=1时字符串比较就行了。那么一种理想的模式匹配就可以的出来了。
KMP匹配过程如下:
第一趟
a b a b c a b c a c b a b
a b c
第二趟
a b a b c a b c a c b a b
a b c a c
第三趟
a b a b c a b c a c b a b
a b c a c
匹配成功,可以看出算法效率提高了不少。
3、剖析KMP算法:
假设(n>m)
主串:s0 s1 s2 s3 s4 s5 s6 …… s(n)
模式:p0 p1 p2 p3 p4……….p(m)
当匹配过程中产生失配(s(i)!=p(j))时,主串的第i个字符应与模式中的哪个字符相比较?假设此时与模式中的第k(k<j)个字符相比较,那么就有p0p1…p(k-1)=s(i-k)s(i-k+1)…s(i-1) --式1(就好像上面中绿的的字符a,这里是从模式中第1个字符开始比较与主串中字符a相同)。
当匹配失配时(s(i)!=p(j)),可以得到p0p1p2p3…p(j-1)=s(i-j)s(i-j+1)…s(i-1) --式2
从式2可以得到p(j-k)p(j-k+1)…p(j-1)=s(i-k)s(i-k+1)..s(i-1) --式3
由式1和式3可以得到p0p1…p(k-1)=p(j-k)p(j-k+1)…p(j-1) --式4
若令next[j]=k,则next[j]表明当模式中第j个字符与主串中相应字符失配时,在模式中需要重新和主串中该字符进行比较的字符位置。那么next 函数定义为:
(1)-1 当j=0时
next[j]= (2)max{k|0<k<j 且式4成立}
(3)0 其他情况
那么此时next值如何求得呢?
由定义知道next[0]=-1;设next[j]=k,这表明在模式串中有这样关系p0p1…p(k-1)=p(j-k)p(j-k+1)…p(j-1) (0<k<j) --式5。此时next[j+1]的值有两中情况:
(1)若p(k)=p(j), 则:p0p1…p(k)=p(j-k)p(j-k+1)…p(j) --式6,即next[j+1]=k+1。
(2)若p(k)!=p(j),则:p0p1…p(k)!=p(j-k)p(j-k+1)…p(j)--式7,此时可以把该问题看成模式匹配的问题,整个模式串既是主串又是模式串,这里应将模式向右移动next[k](模式中第k个字符与主串失配时,需要移动的位置)位置,和主串中的第j个字符相比较。若next[k]=k’,且p(j)=p(k’),则可以得到next[j+1]=next[k]+1即 next[j+1]=next[next[j]]+1。那么还要注意下当模式中上一个字符串与下一个字符串相等时候,它们next值是相等的。
4、KMP算法代码:
- #include "stdafx.h"
- #include "iostream.h"
- #include "string.h"
- //next数组
- void GetNext(char *subStr,int *next)
- {
- int len=strlen(subStr);
- next[0]=-1;
- int i=0,j=-1;
- while(i<len)
- {
- if(j==-1||subStr[i]==subStr[j])
- {
- i++;
- j++;
- //前后缀字符相等
- if(subStr[i]==subStr[j])
- next[i]=next[j];
- else
- next[i]=j;
- }
- else
- j=next[j];
- }
- }
- //KMP算法
- int KMP(char *str,char *subStr)
- {
- int lenStr=strlen(str);
- int lenSubstr=strlen(subStr);
- int i=0,j=0;
- int *next=new int[lenStr];
- GetNext(subStr,next);
- //遍历主串和子串
- while(i<lenStr&&j<lenSubstr)
- {
- //与一般匹配算法增加了j==-1判断
- if(j==-1||str[i]==subStr[j])
- {
- i++;
- j++;
- }
- //j回溯,i不变
- else
- j=next[j];
- }
- delete[] next;
- //返回子串的位置
- if(j>=lenSubstr)
- return i-lenSubstr;
- else
- return -1;
- }
- int main()
- {
- char *str="iloveyouoooyouloveme";
- char *subStr1="youoooyou";
- char *subStr2="youoooyou2";
- cout<<KMP(str,subStr1)<<endl;
- cout<<KMP(str,subStr2)<<endl;
- return 0;
- }
查找子字符串----KMP算法深入剖析的更多相关文章
- 数据结构与算法--KMP算法查找子字符串
数据结构与算法--KMP算法查找子字符串 部分内容和图片来自这三篇文章: 这篇文章.这篇文章.还有这篇他们写得非常棒.结合他们的解释和自己的理解,完成了本文. 上一节介绍了暴力法查找子字符串,同时也发 ...
- C 查找子字符串
自己用 C 写的一个查找子字符串的函数 int findstr(char *str,char *substr) //C实现 find{ if(NULL == str || NULL== substr) ...
- 子字符串查找之————关于KMP算法你不知道的事
写在前面: (阅读本文前需要了解KMP算法的基本思路.另外,本着大道至简的思想,本文的所有例子都会做从头到尾的讲解) 作者翻阅了大量网上现有的KMP算法博客,发现广为流传的竟然是一种不完整的KMP算法 ...
- 数据结构(复习)---------字符串-----KMP算法(转载)
字符串匹配是计算机的基本任务之一. 举例来说,有一个字符串"BBC ABCDAB ABCDABCDABDE",我想知道,里面是否包含另一个字符串"ABCDABD" ...
- [Swift]扩展String类:实现find()查找子字符串在父字符串中的位置
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...
- 字符串 kmp算法 codeforce 625B 题解(模板)
题解:kmp算法 代码: #include <iostream>#include <algorithm>#include <cstring>#include < ...
- 模板 - 字符串 - KMP算法
要先理解前缀函数的定义,前缀函数 \(\pi(i)\) 表示字符串 \(s[0,i]\) 的同时是其最长真前缀及最长真后缀的长度,简单来说就是这个 \(s[0,i]\) 首尾最长的重叠长度(不能完全重 ...
- 字符串KMP算法
讲解:http://blog.csdn.net/starstar1992/article/details/54913261 #include <bits/stdc++.h> using n ...
- 二十六、JavaScript之查找子字符串substring和slice和substr
一.代码如下 二.效果如下 <!DOCTYPE html> <html> <meta http-equiv="Content-Type" conten ...
随机推荐
- CodeForces 158B Taxi(贪心)
贪心,注意优先级,4单独,3与1先匹配,2与2匹配(注意判断2有没有剩下),然后2与两个1匹配,最后4个1匹配就可以了. #include<iostream> #include<cs ...
- CF History(区间合并)
这其实是一个简单的区间合并问题,但是我们第一交是过了,后来学长rejudge,我们又TLE了,这一下不仅耽误了我们的时间,也波动到了我们的心情,原先时间是2s,(原oj就是2s),后来改成了1s,我用 ...
- FZU 2107 Hua Rong Dao(暴力回溯)
dfs暴力回溯,这个代码是我修改以后的,里面的go相当简洁,以前的暴力手打太麻烦,我也来点技术含量.. #include<iostream> #include<cstring> ...
- Quartz总结(二):定时任务中使用业务类(XXService)
零.引言 上一篇文章:讲到了Spring集成Quartz的几种基本方法. 在实际使用的时候,往往会在定时任务中调用某个业务类中的方法,此时使用QuartzJobBean和MethodInvokeJob ...
- SIFT
简介 SIFT(scale invariant feature transform)——尺度不变特征转换,用来检测和描述局部特征,运用范围包括object recognition(目标检测), rob ...
- Android学习笔记之Intent(1)
1.Intent指定启动目标组件 2.Intentfilter描述基本组件所在地址 3.其他包引入资源文件时记得引入R所在的包 package com.jikexueyuan.intent; impo ...
- codeforce 611B New Year and Old Property
暴力搞 #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> u ...
- ZooKeeper 的安装和配置---单机和集群
如题本文介绍的是ZooKeeper 的安装和配置过程,此过程非常简单,关键是如何应用(将放在下节及相关节中介绍). 单机安装.配置: 安装非常简单,只要获取到 Zookeeper 的压缩包并解压到某个 ...
- HDU 2859 Phalanx
简单二维dp.o(n^3)效率过的.不知道有没有o(n^2)的解法. 为了方便点,先左右交换一下. dp[i][j]表示以[i,j]为左上角的最大对称矩阵长度 那么dp[i][j]=min(Max,d ...
- CodeForces 621B Wet Shark and Bishops
记录一下每个对角线上有几个,然后就可以算了 #include<cstdio> #include<cstring> #include<cmath> #include& ...