一个:介绍KMP算法之前,首先解释一下BF算法

(1)BF算法(传统的匹配算法,是最简单的算法)

BF算法是一种常见的模式匹配算法,BF该算法的思想是目标字符串S模式串的第一个字符P的第一个字符,以匹配,如果相等,然后去比较S第二个字和P;若不相等。则比較S的第二个字符和P的第一个字符。依次比較下去。直到得出最后的匹配结果。

   

(2)举例说明:

    S:  ababcababa

    P:  ababa

  BF算法匹配的过程例如以下

           i=0                  i=1                       i=2                 i=3                   i=4

  第一趟:ababcababa         第二趟:ababcababa      第三趟:ababcababa    第四趟:ababcababa    第五趟:ababcababa

    ababa                         ababa                 ababa                 ababa                   ababa

    j=0                           j=1                   j=2                   j=3                     j=4(i和j回溯)



    i=1                           i=2                   i=3                   i=4                     i=3 

 第六趟:ababcababa         第七趟:ababcababa       第八趟:ababcababa     第九趟:ababcababa   第十趟:ababcababa

    ababa                   ababa                        ababa               ababa                   ababa

    j=0                     j=0                          j=1                 j=2(i和j回溯)           j=0



    i=4                     i=5                          i=6                 i=7                     i=8

第十一趟:ababcababa       第十二趟:ababcababa    第十三趟:ababcababa   第十四趟:ababcababa   第十五趟:ababcababa

       ababa                    ababa               ababa                    ababa                ababa

       j=0                       j=0                j=1                      j=2                  j=3

 

    i=9

第十六趟:ababcababa

 ababa

 j=4(匹配成功)

事实上在上面的匹配过程中,有非常多比較是多余的。在第五趟匹配失败的时候,在第六趟。i能够保持不变。j值为2。由于在前面匹配的过程中,对于串S,已知s0s1s2s3=p0p1p2p3。又由于p0!=p1!。所以第六趟的匹配是多余的。又由于p0==p2,p1==p3,所以第七趟和第八趟的匹配也是多余的。

在KMP算法中就省略了这些多余的匹配。

(3)BF代码:

int BFMatch(char* ori,char *des)
{
int i,j;
i = 0;
while(*(ori+i)!='\0')
{
j = 0;
while(*(ori+i)!='\0'&&*(des+j)!='\0'&&*(ori+i)==*(des+j))
{
i++;
j++;
}
if(*(des+j)=='\0')
return i-j;// 返回匹配成功后的src中的開始下标
i = i-j+1;// 回溯到,这次匹配的src中的開始位置的下一个位置
}
return -1;
}

二:KMP算法

(1)KMP算法之所以叫做KMP算法是由于这个算法是由三个人共同提出来的,就取三个人名字的首字母作为该算法的名字。

事实上KMP算法与BF算法的差别就在于KMP算法巧妙的

消除了指针i的回溯问题。仅仅需确定下次匹配j的位置就可以。使得问题的复杂度由O(mn)下降到O(m+n)。

在KMP算法中,为了确定在匹配不成功时,下次匹配时j的位置,引入了next[]数组,next[j]的值表示P[0...j-1]中最长后缀的长度等于同样字符序列的前缀。

对于next[]数组的定义例如以下:

 1) next[j] = -1  j = 0

 2) next[j] = max(k): 0<k<j   P[0...k-1]=P[j-k,j-1]

 3) next[j] = 0  其它



 如:

 P      a    b   a    b   a

 j      0    1   2    3   4

 next    -1   0   0    1   2

 

即next[j]=k>0时,表示P[0...k-1]=P[j-k,j-1]



因此KMP算法的思想就是:在匹配过程称,若发生不匹配的情况,假设next[j]>=0,则目标串的指针i不变。将模式串的指针j移动到next[j]的位置继续进行匹配;若next[j]=-1,则将i右移1位,并将j置0,继续进行比較。

(2)KMP算法通过next数组能够知道目标串中下一个字符是否有必要被检測,这个next数组就是用所谓的“前缀函数(一般数据结构书中的getNext函数)”来存储的。

这个函数可以反映出现失配情况时,系统应该跳过多少无用字符(也即模式串应该向右滑动多长距离)而进行下一次检測

一是这个前缀函数的求法。

二是在得到前缀函数之后。怎么运用这个函数所反映的有效信息避免不必要的检測。

  以下介绍《部分匹配表》是怎样产生的。

  首先,要了解两个概念:"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的所有头部组合。"后缀"指除了第一个字符以外,一个字符串的所有尾部组合。

  "部分匹配值"就是"前缀"和"后缀"的最长的共同拥有元素的长度。以"ABCDABD"为例,

  - "A"的前缀和后缀都为空集,共同拥有元素的长度为0。

  - "AB"的前缀为[A],后缀为[B],共同拥有元素的长度为0;

  - "ABC"的前缀为[A, AB]。后缀为[BC, C]。共同拥有元素的长度0。

  - "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共同拥有元素的长度为0;

  - "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A]。共同拥有元素为"A",长度为1;

  - "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B]。共同拥有元素为"AB",长度为2;

  - "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共同拥有元素的长度为0。

(3) 代码例如以下:

#include <iostream>
#include <cstring> using namespace std;
const int MAX_SIZE = 64; void getNext(char *p,int next[])
{
int j,k;
next[0] = -1;
j = 0;
k = -1;
while(j<strlen(p))
{
if(k==-1 || p[j]==p[k])
{
j++;
k++;
next[j] = k;
}
else
k = next[k];
}
int i;
for(i=1;i<=j;i++)
cout << next[i] << ",";
} int KMPMatch(char *s,char *p)
{
int next[MAX_SIZE];
int i,j;
i = 0;
j = 0;
getNext(p,next);
while(s[i]!='\0')
{
if(j==-1 || s[i]==p[j])
{
i++;
j++;
}
else
{
j = next[j];// 消除指针回溯
}
if(p[j] == '\0')
return i-j;
}
return -1;
} int main()
{
char ori[MAX_SIZE],des[MAX_SIZE];
cout << "请输入两个字符串进行匹配:" << endl;
cin >> ori >> des;
//cout << "匹配结果:" << BFMatch(ori,des) << endl;
cout << "匹配结果:" << KMPMatch(ori,des) << endl;
return 0;
}

(4)总结:KMP是用来匹配test字符串是否是目标串的子串,相当于全然匹配。即測试字符串是否在目标字符串中出现过

三:最长公共子序列(LONGEST COMMEN SUBSEQUENCE)

(1)子序列:不要求连续的,子串是要求连续的。

(2)代码例如以下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream> using namespace std;
const int MAX_SIZE = 100;
int LCSLength(char *s1,char *s2,const int &len1,const int &len2,int lcs[][MAX_SIZE],int b[][MAX_SIZE])
{
int i,j;
for(i=0;i<=len1;i++)
lcs[0][i] = 0;
for(j=1;j<=len2;j++)
lcs[j][0] = 0;
for(i=1;i<=len1;i++)
{
for(j=1;j<=len2;j++)
{
if(s1[i-1] == s2[j-1])
{
lcs[i][j] = lcs[i-1][j-1] + 1;
b[i][j] = 0;
}
else if(lcs[i-1][j] >= lcs[i][j-1])
{
lcs[i][j] = lcs[i-1][j];
b[i][j] = 1;
}
else
{
lcs[i][j] = lcs[i][j-1];
b[i][j] = -1;
}
}
}
return lcs[len1][len2];
} void PrintLCS(char *s1,int b[][MAX_SIZE],int i,int j)
{
if(i==1 || j==0)
return;// 一定要与返回啊啊
if(b[i][j] == 0)
{
PrintLCS(s1,b,i-1,j-1);
cout << s1[i-1];
}
else if(b[i][j] == 1)
{
PrintLCS(s1,b,i-1,j);
}
else
{
PrintLCS(s1,b,i,j-1);
}
}
int main()
{
int len1,len2;
int lcs[MAX_SIZE][MAX_SIZE],b[MAX_SIZE][MAX_SIZE];
char s1[MAX_SIZE],s2[MAX_SIZE];
int ans;
while(cin >> s1 >> s2)
{
len1 = strlen(s1);
len2 = strlen(s2);
ans = LCSLength(s1,s2,len1,len2,lcs,b);
cout << ans << endl;
PrintLCS(s1,b,len1,len2);
cout << endl;
}
return 0;
}//

版权声明:本文博客原创文章。博客,未经同意,不得转载。

KMP该算法解释(最长公共子)的更多相关文章

  1. 使用后缀数组寻找最长公共子字符串JavaScript版

    后缀数组很久很久以前就出现了,具体的概念读者自行搜索,小菜仅略知一二,不便讨论. 本文通过寻找两个字符串的最长公共子字符串,演示了后缀数组的经典应用. 首先需要说明,小菜实现的这个后缀数组算法,并非标 ...

  2. 算法练习——最长公共子序列的问题(LCS)

    问题描述: 对于两个序列X和Y的公共子序列中,长度最长的那个,定义为X和Y的最长公共子序列.X  Y   各自字符串有顺序,但是不一定需要相邻. 最长公共子串(Longest Common Subst ...

  3. Java实现 蓝桥杯VIP 算法提高 最长公共子序列

    算法提高 最长公共子序列 时间限制:1.0s 内存限制:256.0MB 问题描述 给定两个字符串,寻找这两个字串之间的最长公共子序列. 输入格式 输入两行,分别包含一个字符串,仅含有小写字母. 输出格 ...

  4. uva 10066 The Twin Towers (最长公共子)

    uva 10066 The Twin Towers 标题效果:最长公共子. 解题思路:最长公共子. #include<stdio.h> #include<string.h> # ...

  5. ACM/ICPC 之 最长公共子序列计数及其回溯算法(51Nod-1006(最长公共子序列))

    这道题被51Nod定为基础题(这要求有点高啊),我感觉应该可以算作一级或者二级题目,主要原因不是动态规划的状态转移方程的问题,而是需要理解最后的回溯算法. 题目大意:找到两个字符串中最长的子序列,子序 ...

  6. 【算法】最长公共子序列(nlogn)

    转载注明出处:http://blog.csdn.net/wdq347/article/details/9001005 (修正了一些错误,并自己重写了代码) 最长公共子序列(LCS)最常见的算法是时间复 ...

  7. POJ 2774 后缀数组:查找最长公共子

    思考:其实很easy.就在两个串在一起.通过一个特殊字符,中间分隔,然后找到后缀数组的最长的公共前缀.然后在两个不同的串,最长是最长的公共子串. 注意的是:用第一个字符串来推断是不是在同一个字符中,刚 ...

  8. 算法实践--最长公共子序列(Longest Common Subsquence)

    什么是最长公共子序列 X=ACCG Y=CCAGCA 长度为1的公共子序列: {A} {C} {G} 长度为2的公共子序列:{AC} {CC} {CG} {AG} 长度为3的公共子序列:{ACG} 长 ...

  9. Java算法练习——最长公共前缀

    题目链接 题目描述 编写一个函数来查找字符串数组中的最长公共前缀. 如果不存在公共前缀,返回空字符串 "". 说明: 所有输入只包含小写字母 a-z . 示例 1 输入: [&qu ...

随机推荐

  1. C++ 中的比較函数

    在敲代码的时候,排序是一种和经常使用的算法.在排序中.比較又是当中最经常使用的操作.这里,我们来分析一下C++中的比較问题. 当中,基本数据类型int. float.string等已经提供了默认的比較 ...

  2. 【Java基础】异常的简单分类与处理

    Java中所有的异常都继承自Throwable类,Throwable类的已知子类有Error和Exception. Error是指系统出现的错误,这种错误出现的时候,我们的程序无能为力,所以不需要进行 ...

  3. 标准I/O缓冲:全缓冲、行缓冲、无缓冲

    说明:我仅仅对网络资源进行了整合,方便学习-.- 基于流的操作终于会调用read或者write函数进行I/O操作.为了使程序的执行效率最高,流对象一般会提供缓冲区,以降低调用系统I/O库函数的次数. ...

  4. hive load from hdfs出错

    使用hive load从hdfs中load data的时候,hiveql如下: load data inpath 'hdfs://192.168.0.131:9000/hive/test.log' o ...

  5. ORACLE触发特定的解释

    ORACLE PL/SQL编程八: 把触发器说透 本篇主要内容例如以下: 8.1 触发器类型 8.1.1 DML触发器 8.1.2 替代触发器 8.1.3 系统触发器 8.2 创建触发器 8.2.1 ...

  6. HTML介绍JS

    首先,该脚本的链接插入HTML代码: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvU2h1aVRpYW5OYWlMdW8=/font/5a6L5L2T/f ...

  7. 【Spring】Spring学习笔记-01-入门级实例

    听说当前Spring框架很流行,我也准备好好学学Spring开发,并将学习的过程和大家分享,希望能对志同道合的同学有所帮助. 以下是我学习Spring的第一个样例. 1.Spring开发环境的搭建 我 ...

  8. offsetTop和scrollTop差异

    最近写组件,这两个属性的结果搞的有点晕,我检查的文件及资料,这两个性质如下面总结: 他一直在offsetLeft.offsetTop,scrollLeft.scrollTop这些方法都是非常迷茫,花一 ...

  9. DSR on Openstack POC

    watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbWFvbGlwaW5nNDU1bWxwNDU1/font/5a6L5L2T/fontsize/400/fil ...

  10. 【Web探索之旅】第二部分第一课:客户端语言

    内容简介 1.第二部分第一课:客户端语言 2.第二部分第二课预告:服务器语言 第二部分:Web编程语言和工具 大家好.上一个部分我们学习了Web的一些基本概念: 什么是Web? Internet和We ...