扩展KMP算法 Extend KMP
来源:互联网 发布:萧山网络问政 高桥 编辑:程序博客网 时间:2024/05/19 12:24
From http://noalgo.info/340.html
前面介绍过经典的KMP算法,本文继续介绍KMP算法的扩展,即扩展KMP算法:给定一个较长的主字符串S和一个较短的模式串T,用m、n分别表示S和T的长度(即m=|S|,n=|T|),定义extend[i]=S[i..n]与T的最长公共前缀的长度,求extend[i]。
为什么说这是KMP算法的扩展呢?显然,如果在S的某个位置x有extend[x]==n,则可知在S中可以找到T,并且首位置是x,这正是KMP算法要解决的问题。而且,扩展KMP算法能找到S中所有T的匹配,更一般地,可以知道S中以每个字符开始的后缀与T的最大的匹配长度(最长公共前缀长度)。
以下所有例子及代码实现中,字符串下标均从0开始!
一 基本思想
扩展KMP算法可以在线性时间内求出extend数组的值。
令S=aaaaabbb,T=aaaaac,如下
S:aaaaabbbT:aaaaac首先,通过6次比较可以知道,extend[0]=5。然后要计算extend[1],如下
S:aaaaabbbT: aaaaac可以看到,extend[1]=4。但是这里是否要经过5次比较才可以得到4这个结果呢?其实,正如KMP算法一样,这里也可以充分利用在前面匹配中已得到的信息来简化当前的匹配。
事实上,由extend[0]=5我们可以知道,S[0..4]=T[0..4]
因为现在extend[1]考虑的是S中第1个字符开始的后缀,则取出S的下标从1开始的部分,有S[1..4]=T[1..4]
计算extend[1],实际上用S中S[1]开始的后缀去匹配T,根据上式可以知道,S中前面4个字符的匹配就是T中T[1]开始的后缀的匹配,即用T来跟T自己匹配!
假设我们初始化了一个数组next,next[i]表示T[i..n]跟T的最长公共前缀的长度。对于这里,有next[1]=4,即T[1..4]=T[0..3]
综合以上的两个等式,有S[1..4]=T[1..4]=T[0..3]
即S的第1到4个字符跟T的前4个字符是匹配的,我们可以直接从S的第5个字符开始比较。
S:aaaaabbbT: aaaaac这里发现,S[5]=b,T[4]=a,于是S[5]!=T[4],于是知道extend[1]=4。
二 具体算法之extend数组
以上只是算法一个简单直观的描述,下面介绍算法的具体实现方法。
扩展KMP算法顺序遍历S中的每个字符,遍历过程中依次计算每个位置的extend值。
假设当前遍历到位置i,即extend[0..i-1]这i个位置的值已经计算得到。算法在比遍历过程中记录了匹配成功的字符的最远位置p及这次匹配的起始位置a,事实上,匹配位置x时匹配成功的最远位置是x+extend[x]-1,p就是x=0…i-1时得到的最大值,a就是取到最大值时对应的那个x。
S串: 0 1 2 … a … i-1 i … p …
对于a这个位置,我们有 S[a..p] = T[0..p-a]
取i开始的那一段,则有 S[i..p] = T[i-a..p-a]
要求extend[i],即S[i...]匹配T[0...],我们可以先用T[i-a...]来匹配T[0...],这正是next[i-a]表示的含义:T中第位置i-a开始的后缀跟T的最长公共前缀长度。令L=next[i-a],分以下两种情况讨论:
- i + L < p,如下所示
S:012…a…i-1ii+1…i+L……P…T: 01…LL+1………S跟T在绿色部分一定匹配,且根据next数组的含义,红色部分一定不匹配,因为如红色部分匹配的话,S[i..p]可以看成是T[i-a..p-a],则T[i-a...]与T[0...]的匹配会大于L,与最长公共前缀矛盾。
于是,无需任何比较就可以得到,extend[i]=L,同时a和p保持不变。 - i + L >= p,如下所示
S:012…ai-1i…pp+1………T: 0…p-ip-i+1…L…这时候绿色部分是已经匹配成功的,但因为之前匹配到最远的位置只有p,所以红色部分p+1及其以后位置的匹配结果是未知的。所以接下来要从S[p+1]和T[p-i+1]开始逐个向右匹配,直到匹配失败为止。匹配完之后,可以得到extend[i]的值和p的值,同时需要更新a的值为当前的i。
这就是扩展KMP算法的主要思想,下面是其C++实现的代码。注意程序中为了实现的方便,p表示的是匹配成功的字符的最远位置的下一个位置,即下次匹配直接从S[p]开始(上面的是从S[p+1]开始)。同时使用了变量j跟踪T中跟S中p位置对应的位置,注意遍历S时i在递增的同时,j需要递减。
可以看到,扩展KMP算法主要依赖指针p的值计算extend数组,而p只会扫描主串S一遍,于是算法的复杂度是线性的,O(m+n)。
三 具体算法之next数组
但还有一个问题没有解决,next数组怎么得到呢?
根据定义,next[i]表示T[i..n]和T[0..n]的最长公共前缀的长度,相当于以上算法中把S换成T的版本,即T自己和自己的一个匹配。不过这里next数组是从1开始的,next[0]匹配的肯定是整个串的长度n。
具体C++代码如下:
可以看到,KMP算法和扩展KMP算法再具体求解next数组时都是利用自身和自身匹配的一个思路,于是求next数组的代码跟主体匹配的代码是非常相似的,在编写代码时,可以利用这个相似性快速编码,并减少出错概率。
KMP算法和扩展KMP算法都是充分利用前面匹配的结果,在匹配失败时大幅度移动指针,直接跳过不可能的状态,减少算法的复杂度。
My Summary:
s[a,p-1] = t[0, p-a-1] -> s[i,p-1]=t[i-a, p-a-1]
t[i-a,next[i-a]+i-a-1] = t[0, next[i-a]-1]
So if next[i-a]+i-a-1 >= p-a-1, extend j,p, extend[i]=j
else extend[i]=next[i-a]
My Code:
void getNextArr(const char *t, vector<int>& nextarr) { int lt = strlen(t); nextarr.resize(lt,lt); for (int i = 1, j = -1, a = 0, p = 0; i < lt; ++i, --j) { if (j < 0 || i + nextarr[i - a] >= p) { if (j < 0) j = 0, p = i; while (p < lt && t[p] == t[j]) ++p, ++j; nextarr[i] = j, a = i; } else { nextarr[i] = nextarr[i - a]; } } } void getExtendArr(const char *s, const char *t, vector<int>& extend) { int slen = strlen(s), tlen = strlen(t); extend.resize(tlen); vector<int> nextarr; getNextArr(t, nextarr); for (int i = 0, j = -1, a = 0, p = 0; i < tlen; ++i, --j) { if (j < 0 || i + nextarr[i - a] >= p) { if (j < 0) j = 0, p = i; while (p < slen && j < tlen && s[p] == t[j]) ++p, ++j; extend[i] = j, a = i; } else { extend[i] = nextarr[i - a]; } } }
Debug Code:
#include <stdio.h>#include <iostream>#include <vector>using namespace std;class Solution {public: void getExtendNext(const char *t, int *next) { int lt = strlen(t); for (int i = 1, j = -1, a, p; i < lt; i++, j--) { if (j < 0 || i + next[i - a] >= p) { if (j < 0)j = 0, p = i; while (p < lt && t[j] == t[p])j++, p++; next[i] = j, a = i; } else { next[i] = next[i - a]; } } } void getExtend(const char *s, const char *t, int *extend) { int ls = strlen(s), lt = strlen(t); int next[100]; getExtendNext(t, next); for (int i = 0, j = -1, a, p; i < ls; i++, j--) if (j < 0 || i + next[i - a] >= p) { if (j < 0)j = 0, p = i; while (p < ls && j < lt && s[p] == t[j])j++, p++; extend[i] = j, a = i; } elseextend[i] = next[i - a]; } void getNextArr(const char *t, vector<int>& nextarr) { int lt = strlen(t); nextarr.resize(lt,lt); for (int i = 1, j = -1, a = 0, p = 0; i < lt; ++i, --j) { if (j < 0 || i + nextarr[i - a] >= p) { if (j < 0) j = 0, p = i; while (p < lt && t[p] == t[j]) ++p, ++j; nextarr[i] = j, a = i; } else { nextarr[i] = nextarr[i - a]; } } } void getExtendArr(const char *s, const char *t, vector<int>& extend) { int slen = strlen(s), tlen = strlen(t); extend.resize(tlen); vector<int> nextarr; getNextArr(t, nextarr); for (int i = 0, j = -1, a = 0, p = 0; i < tlen; ++i, --j) { if (j < 0 || i + nextarr[i - a] >= p) { if (j < 0) j = 0, p = i; while (p < slen && j < tlen && s[p] == t[j]) ++p, ++j; extend[i] = j, a = i; } else { extend[i] = nextarr[i - a]; } } } char *strStr(char *haystack, char *needle) { // Note: The Solution object is instantiated only once and is reused by each test case. if (needle == NULL || haystack == NULL) return NULL; int i = 0, j = -1, needlelen = strlen(needle), haystacklen = strlen(haystack); if (needlelen > haystacklen) return NULL; if (needlelen == 0) return haystack; vector<int> next(needlelen + 1, -1); while (needle[i]) { if (j == -1 || needle[i] == needle[j]) { ++i; ++j; next[i] = j; } else j = next[j]; } i = 0; j = 0; while (haystack[i]) { if (j == -1 || haystack[i] == needle[j]) { ++i; ++j; if (needle[j] == '\0') return haystack + i - j; } else j = next[j]; } return NULL; }};int main() { Solution solution; const char *ch = "aaaaabbb"; const char *s = "aaaaabbb"; const char *t = "aaaaac"; int nextarr[8]; vector<int> next, extend; //solution.getNextArr(ch, next); //solution.getExtendNext(ch, nextarr); solution.getExtend(s, t, nextarr); solution.getExtendArr(s, t, extend); return 0;}
- 扩展KMP算法 Extend KMP
- 字符串匹配-扩展KMP(Extend-KMP)
- 扩展KMP算法(Extend KMP) 学习小记 Hdu 4333 Revolving Digits
- 扩展的KMP算法,
- 扩展KMP算法
- 扩展KMP算法实现
- 扩展KMP算法
- 扩展KMP算法
- 扩展KMP算法
- KMP算法扩展
- 扩展kmp算法
- 扩展kmp算法
- 【hdu4333】扩展kmp算法
- 基础算法 扩展KMP
- 扩展kmp算法讲解
- 【算法】(扩展)KMP+manacher
- 扩展KMP算法
- 扩展kmp算法
- WebRadioGroup.Select
- 线程池、内存池、连接池、对象池
- 又一个被坑的题 读题太不细心了 hdu 1260 简单dp
- CodeForces 56E-Find the Path(技巧)
- 排序算法之堆排序
- 扩展KMP算法 Extend KMP
- Ubuntu Linux下为PHP5安装cURL
- 23-编码实现软件界面与通知
- 建站:域名注册,从国外注册干净的域名,用虚拟信用卡取得最优价格
- UVA - 10790 How Many Points of Intersection?
- sed高级用法
- [编程之美] PSet2.16 求数组中最长的递增子序列
- 算法导论 第7章 快速排序
- Swt中实现对TitleAreaDialog窗口的关闭进行监听