您的位置:首页 > 其它

理解 KMP 算法

2017-04-14 09:40 218 查看
/*
比如 关键字 key ('abcf') 匹配字符串SS ('abcd abcf')

当比较到d的时候, 发现不对, 那为了节省效率, 可以直跳过abc,
跳跃到d开始再比较, 这里是没问题的

但是当 关键字 key('abcabd') 匹配字符串 SS( 'abcabcabd')
比较到第二个c的时候, 发现d != c ,显然是不能直接跳跃到c开始比较的

应该从第二个ab开始, 要找出这个ab可利用 前缀 = 后缀的方法

跳3位, 就到了ab, 这3位是怎么算出来的, 已经匹配的abcab 5位 - ab的 2位得到

比如 key(abcabd) 切前面5位就是 abcab

前缀是 a   ab   abc   abca
后缀   b   ab   cab   bcab

可以看到 ab是在关键字切割成一部分后, 存在前缀和后缀相等的情况

这种情况在匹配字符串SS 的时候, 第一次遍历SS的时候,如果abca都匹配成功 ,a就匹配了两次,  如果或者abcab都匹配成功, ab就匹配了两次

所以这个关键字可以标记成 [ a, b, c, \1, \2, d ]


   反过来

    1. 如果一个关键字没有 前缀和后缀相等的情况, 则可以直接跳过已经匹配的, 如

     SS: abcdffff

    key: abcde

    2. 如果有前缀 或者情况, 但匹配到 e的时候才发现不匹配, 而且前一个f在 map表为0, 也是可以直接跳过的

因为既然最后的 abf, 在前缀不存在abf, 所以不需要跳到第二个ab开始去匹配, 直接跳到第二个f就可以了

   SS: abcdabf fgggg
    
    key: abcdabf e
    
   3. 再看一个复杂一点的情况,
     
    SS: abc abc accab

   key: abc abc ab
map: 000 123 12
  
根据前面 已匹配 - 上一个map值; 会跳到第3个a 去比较, 为什么不跳到第二个abc开始呢,

*/ function getMap(str) { var prefix = []; var suffix = []; var map = []; for (var i = 0, j = str.length; i < j; i++) { var newStr = str.substring(0, i + 1); if (newStr.length == 1) { map[i] = 0; } else {       //每跳一个字符, 就查看这截字符串 有没有前缀后缀匹配的情况 for (var k = 0; k < i; k++) { //取前缀 prefix[k] = newStr.slice(0, k + 1); //取后缀 suffix[k] = newStr.slice(-k - 1); //如果前后缀相等 if (prefix[k] == suffix[k]) { // 1 2 map[i] = prefix[k].length; } } if (!map[i]) { map[i] = 0; } } } return map; } function KMP(SS, key) { //生成匹配表 [0,0,0,1,2,0] var part = getMap(key); var sslen = SS.length; var keylen = key.length; var result; var i = 0; var j = 0; for (i=0; i < SS.length; i++) { //最外层循环,主串 //子循环 for ( j = 0; j < keylen; j++) { //如果ss字符 和key 字符相等 if (key.charAt(j) == SS.charAt(i)) { //如果最后一个字符也相等, 说明匹配完成, 有结果, 跳出内循环 if (j == keylen - 1) { result = i - j; break; } else { i++; } } else {         //abcabf
        //00012 part[j] 是 f 的位置
        //abcabd
       if (j > 1 && part[j - 1] > 0) { // 已匹配的 减去 部分相等前缀长度, 最好调试一下这里
           //回溯 i -= part[j-1] console.log(i) } else { //跳过已经匹配的 或者 没有匹配的 //i = i - j;
          

          //如果有匹配的字符, 因为排除了kmp情况, 直接跳过已经匹配过的
                 if(j>0){
                    //i 其实保持不变, 因为跳出内循环后, 外循环i会+1
                    i -= 1;
                    console.log(i + 'bb')
                 }else{
                   //移动一位
                   i = i - j;
                 }

}
         //因为不匹配, 所以跳出本次关键字对比
break;
}

}

//如果有结果退出当前循环
if (result || result == 0) {
break;
}
}

//如果没有结果
if (result || result == 0) {
return result
} else {
return -1;
}
}
var s = "abcabfababcabffffffabcabd";
var k = "abcabd";

var r =  KMP(s,k)

console.log(r)

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: