Chinaunix首页 | 论坛 | 博客
  • 博客访问: 571215
  • 博文数量: 207
  • 博客积分: 10128
  • 博客等级: 上将
  • 技术积分: 2440
  • 用 户 组: 普通用户
  • 注册时间: 2004-10-10 21:40
文章分类

全部博文(207)

文章存档

2009年(200)

2008年(7)

我的朋友

分类:

2009-04-01 15:04:29

KMP算法是一个快速的匹配算法, 它是对一般的字符串匹配算法的优化, 对其理解, 可以从一般字符串匹配开始!

(1) 一般字符串匹配算法:

int index(char * s, char * t, int pos)
{
int i = pos, j = 0; int sLen = strlen(s); int tLen = strlen(t);

while (i < sLen && j < tLen)
{
     if (s[i] == t[j] )
      { i++; j++; }
     else
      { i = i - j + 1;   j = 0; } // 回溯到起始比较字符的下一个字符
}

if (j == tLen)
    return i - j ; //匹配成功, 返回子串在查找串中的位子!
else
    return 0;
}

这种算法的效率比较低,主要低在回溯那一步, KMP算法,就是针对这点而研究出来的, 具体而言, 它将问题进行了转化, 本来是原串s的回溯, 现在变成了对串t 的规律研究:
假设, s[i] == t[j] 并且 s[i+1] != t[j+1]; 那么我们需要找这样一个j' , 使得t[1..j]中的头j'个字母和末j'个字母完全相等(这样j变成了j'后才能继续保持i和j的性质), 同时, j' 当然要越大越好。

新的j可以取多少与i无关,只与t串有关。我们完全可以预处理出这样一个数组Next[j],表示当匹配到t数组的第j个字母而第j+1个字母不能匹配了时,新的j最大是多少。Next[j]应该是所有满足 t[1..Next[j]] = t [j-Next[j]+1..j]的最大值。

现在假设该Next[]数组已经存在(并且规定Next[1] = 0;), 那么上面的算法按照 KMP的思路可以改成 (这里重写了下):

int Index_KMP(String S,String T,int pos)
{
i=pos;j=1;//这里S和T 串的第1个元素下标是都是1, 这是为了表述的方便
while(i<=S.Length && j<=T.Length)
{
    if(j==0 || S[i]==T[j]) {++i;++j;} //注意到这里的j==0,和++j的作用就知道为什么规定next[1]=0的好处了
    else j=next[j]; // i 不变(不回溯)j 跳动
}
if(j>T.Length) return i-T.Length; // 匹配成功
else return 0;
}

下面主要就是构造Next数组, 根据Next数组的意义, 它本质上是T串的自身部分匹配,那么,我只要将T串和T串自身来一次匹配就可以求出来了,这里的匹配过程不是从头一个一个匹配,而是从T[1]和T[2]开始匹配,考虑到C中习惯于下标从0开始计算, 故这里从T[0], T[1]开始匹配, 其程序为:
void get_next(char * T, int next[])
{
int i = 0, j = -1; next[0] = -1;
while (i < strlen(T))
{
      if (j == -1 || T[i] == T[j])
      { ++i; ++j; next[i] = j;} //每次增加i ,都为next[i]赋值, 该值是T串自匹配时, 已经比较了的下标 j
      else
        j = next[j]; // j 跳回到以前可以匹配的值
}
}

注: T[i]==T[j]以及 i 前面的、j前面的都匹配的情况下,于是先自增,然后记下来next[i]=j,这样每当i有自增就会求得一个next[i], 而j一定会小于等于i,于是对于已经求出来的next,可以继续求后面的next,而next[1]=0是已知,所以整个就这样递推的求出来了,方法非常 巧妙。

注意到下面的匹配情况:

...aaac...
   aaaa.
T串中的'a'和S串中的'c'失配,而'a'的next值指的还是'a',那同样的比较还 是会失配,而这样的比较是多余的,如果我事先知道,当T[i]==T[j],那next[i]就设为next[j],在求next值的时候就已经比较了, 这样就可以去掉这样的多余的比较。于是稍加改进得到:

void get_nextval(char * T,int next[])
{
int i=0, j = -1; next[0]= -1;
while(i< strlen(T))
{
    if(j == -1 || T[i]==T[j])
    { ++i;++j;
      if(T[i]!=T[j]) next[i]=j;
      else next[i]=next[j];//消去多余的可能的比较,next再向前跳 (这里j 肯定是小于i的)
    }
    else j=next[j];
}
}

匹配算法不变。

附: 优化 get_nextval() 前后, next数组中的值对比:
int main()
{
     char p[] ="abababcdabcd";
     int next[256];
     int i;
   
get_nextval(p,next);
     printf("The length of P:%d\n",strlen(p));
     for(i=0;i         printf("next[%d]:%d\n",i,next[i]);
    return 0;
}

优化前的值:
C:\zyf\C_C++>kmp
next[0]: -1
next[1]: 0
next[2]: 0
next[3]: 1
next[4]: 2
next[5]: 3
next[6]: 4

next[7]: 0
next[8]: 0
next[9]: 1
next[10]: 2
next[11]: 0

优化后的值:
C:\zyf\C_C++>kmp
next[0]: -1
next[1]: 0
next[2]: -1
next[3]: 0
next[4]: -1
next[5]: 0
next[6]: 4

next[7]: 0
next[8]: -1
next[9]: 0
next[10]: 2
next[11]: 0
阅读(882) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~