亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

JavaScript中數據結構與算法(五):經典KMP算法

2019-11-20 12:12:46
字體:
來源:轉載
供稿:網友

KMP算法和BM算法

KMP是前綴匹配和BM后綴匹配的經典算法,看得出來前綴匹配和后綴匹配的區別就僅僅在于比較的順序不同

前綴匹配是指:模式串和母串的比較從左到右,模式串的移動也是從 左到右

后綴匹配是指:模式串和母串的的比較從右到左,模式串的移動從左到右。

通過上一章顯而易見BF算法也是屬于前綴的算法,不過就非常霸蠻的逐個匹配的效率自然不用提了O(mn),網上蛋疼的KMP是講解很多,基本都是走的高大上路線看的你也是一頭霧水,我試圖用自己的理解用最接地氣的方式描述

KMP

KMP也是一種優化版的前綴算法,之所以叫KMP就是Knuth、Morris、Pratt三個人名的縮寫,對比下BF那么KMP的算法的優化點就在“每次往后移動的距離”它會動態的調整每次模式串的移動距離,BF是每次都+1,

KMP則不一定

如圖BF與KMP前置算法的區別對比

我通過圖對比我們發現:

在文本串T中搜索模式串P,在自然匹配第6個字母c的時候發現二等不一致了,那么BF的方法,就是把整個模式串P移動一位,KMP則是移動二位.

BF的匹配方法我們是知道的,但是KMP為什么會移動二位,而不是一位或者三位四位呢?

這就上一張圖我們講解下,模式串P在匹配了ababa的時候都是正確的,當到c的時候才是錯誤,那么KMP算法的想法是:ababa是正確的匹配完成的信息,我們能不能利用這個信息,不要把"搜索位置"移回已經比較過的位置,繼續把它向后移,這樣就提高了效率。

那么問題來了, 我怎么知道要移動多少個位置?

這個偏移的算法KMP的作者們就給我們總結好了:

復制代碼 代碼如下:

移動位數 = 已匹配的字符數 - 對應的部分匹配值

偏移算法只跟子串有關系,沒文本串沒毛線關系,所以這里需要特別注意了

那么我們怎么理解子串中已匹配的字符數與對應的部分匹配值?

已匹配的字符:

復制代碼 代碼如下:

T : abababaabab
p : ababacb

p中紅色的標記就是已經匹配的字符,這個很好理解

部分匹配值:

這個就是核心的算法了,也是比較難于理解的

假如:

復制代碼 代碼如下:

T:aaronaabbcc
P:aaronaac

我們可以觀察這個文本如果我們在匹配c的時候出錯,我們下一個移動的位置就上個的結構來講,移動到那里最合理?
復制代碼 代碼如下:

aaronaabbcc
     aaronaac

那么就是說:在模式文本內部,某一段字符頭尾都一樣,那么自然過濾的時候可以跳過這一段內容了,這個思路也是合理的

 

知道了這個規律,那么給出來的部分匹配表算法如下:

首先,要了解兩個概念:"前綴"和"后綴"。 "前綴"指除了最后一個字符以外,一個字符串的全部頭部組合;"后綴"指除了第一個字符以外,一個字符串的全部尾部組合。

"部分匹配值"就是"前綴"和"后綴"的最長的共有元素的長度”

我們看看aaronaac的如果是BF匹配的時候劃分是這樣的

BF的位移: a,aa,aar,aaro,aaron,aarona,aaronaa,aaronaac

那么KMP的劃分呢?這里就要引入前綴與后綴了

我們先看看KMP部分匹配表的結果是這樣的:

復制代碼 代碼如下:

a   a  r  o  n  a  a  c
[0, 1, 0, 0, 0, 1, 2, 0]

肯定是一頭霧水,不急我們分解下,前綴與后綴

復制代碼 代碼如下:

匹配字符串 :“Aaron”
前綴:A,Aa, Aar ,Aaro
后綴:aron,ron,on,n

移動的位置:其實就是針對每一個已匹配的字符做前綴與后綴的對比是否相等,然后算出共有的長度

部分匹配表的分解

KMP中的匹配表的算法,其中p表示前綴,n表示后綴,r表示結果

復制代碼 代碼如下:

a,         p=>0, n=>0  r = 0

aa,        p=>[a],n=>[a] , r = a.length => 1

aar,       p=>[a,aa], n=>[r,ar]  ,r = 0

aaro,      p=>[a,aa,aar], n=>[o,ra,aro] ,r = 0

aaron      p=>[a,aa,aar,aaro], n=>[n,on,ron,aron] ,r = 0

aarona,    p=>[a,aa,aar,aaro,aaron], n=>[a,na,ona,rona,arona] ,r = a.lenght = 1

aaronaa,   p=>[a,aa,aar,aaro,aaron,aarona], n=>[a,aa,naa,onaa,ronaa,aronaa] ,  r = Math.max(a.length,aa.length) = 2

aaronaac   p=>[a,aa,aar,aaro,aaron,aarona], n=>[c,ac,aac,naac,onaac,ronaac]  r = 0

類似BF算法一下,先分解每一次可能匹配的下標的位置先緩存起來,在匹配的時候通過這個《部分匹配表》來定位需要后移動的位數

所以最后aaronaac的匹配表的結果 0,1,0,0,0,1,2,0 就是這么來的

下面將會實現JS版的KMP,有2種

KMP實現(一):緩存匹配表的KMP

KMP實現(二):動態計算next的KMP


KMP實現(一)

匹配表

KMP算法中最重要的就是匹配表,如果不要匹配表那就是BF的實現,加上匹配表就是KMP了

匹配表決定了next下一個位移的計數

針對上面匹配表的規律,我們設計一個kmpGetStrPartMatchValue的方法

function kmpGetStrPartMatchValue(str) {   var prefix = [];   var suffix = [];   var partMatch = [];   for (var i = 0, j = str.length; i < j; i++) {    var newStr = str.substring(0, i + 1);    if (newStr.length == 1) {     partMatch[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]) {       partMatch[i] = prefix[k].length;      }     }     if (!partMatch[i]) {      partMatch[i] = 0;     }    }   }   return partMatch;  }

完全按照KMP中的匹配表的算法的實現,通過str.substring(0, i + 1) 分解a->aa->aar->aaro->aaron->aarona->aaronaa-aaronaac

然后在每一個分解中通過前綴后綴算出共有元素的長度

回退算法

KMP也是前置算法,完全可以把BF那一套搬過來,唯一修改的地方就是BF回溯的時候直接是加1,KMP在回溯的時候我們就通過匹配表算出這個next值即可

//子循環for (var j = 0; j < searchLength; j++) {  //如果與主串匹配  if (searchStr.charAt(j) == sourceStr.charAt(i)) {    //如果是匹配完成    if (j == searchLength - 1) {     result = i - j;     break;    } else {     //如果匹配到了,就繼續循環,i++是用來增加主串的下標位     i++;    }  } else {   //在子串的匹配中i是被疊加了   if (j > 1 && part[j - 1] > 0) {    i += (i - j - part[j - 1]);   } else {    //移動一位    i = (i - j)   }   break;  }}

紅色標記的就是KMP的核心點 next的值  = 已匹配的字符數 - 對應的部分匹配值

完整的KMP算法

<!doctype html><div id="test2"><div><script type="text/javascript">   function kmpGetStrPartMatchValue(str) {   var prefix = [];   var suffix = [];   var partMatch = [];   for (var i = 0, j = str.length; i < j; i++) {    var newStr = str.substring(0, i + 1);    if (newStr.length == 1) {     partMatch[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]) {       partMatch[i] = prefix[k].length;      }     }     if (!partMatch[i]) {      partMatch[i] = 0;     }    }   }   return partMatch;  }function KMP(sourceStr, searchStr) {  //生成匹配表  var part     = kmpGetStrPartMatchValue(searchStr);  var sourceLength = sourceStr.length;  var searchLength = searchStr.length;  var result;  var i = 0;  var j = 0;  for (; i < sourceStr.length; i++) { //最外層循環,主串    //子循環    for (var j = 0; j < searchLength; j++) {      //如果與主串匹配      if (searchStr.charAt(j) == sourceStr.charAt(i)) {        //如果是匹配完成        if (j == searchLength - 1) {         result = i - j;         break;        } else {         //如果匹配到了,就繼續循環,i++是用來增加主串的下標位         i++;        }      } else {       //在子串的匹配中i是被疊加了       if (j > 1 && part[j - 1] > 0) {        i += (i - j - part[j - 1]);       } else {        //移動一位        i = (i - j)       }       break;      }    }    if (result || result == 0) {     break;    }  }  if (result || result == 0) {   return result  } else {   return -1;  }} var s = "BBC ABCDAB ABCDABCDABDE"; var t = "ABCDABD"; show('indexOf',function() {  return s.indexOf(t) }) show('KMP',function() {  return KMP(s,t) }) function show(bf_name,fn) {  var myDate = +new Date()  var r = fn();  var div = document.createElement('div')  div.innerHTML = bf_name +'算法,搜索位置:' + r + ",耗時" + (+new Date() - myDate) + "ms";   document.getElementById("test2").appendChild(div); }</script></div></div>

KMP(二)

第一種kmp的算法很明顯,是通過緩存查找匹配表也就是常見的空間換時間了。那么另一種就是時時查找的算法,通過傳遞一個具體的完成字符串,算出這個匹配值出來,原理都一樣

生成緩存表的時候是整體全部算出來的,我們現在等于只要挑其中的一條就可以了,那么只要算法定位到當然的匹配即可

next算法

function next(str) {  var prefix = [];  var suffix = [];  var partMatch;  var i = str.length  var newStr = str.substring(0, i + 1);  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]) {    partMatch = prefix[k].length;   }  }  if (!partMatch) {   partMatch = 0;  }  return partMatch;}

其實跟匹配表是一樣的,去掉了循環直接定位到當前已成功匹配的串了

完整的KMP.next算法

<!doctype html><div id="testnext"><div><script type="text/javascript">   function next(str) {    var prefix = [];    var suffix = [];    var partMatch;    var i = str.length    var newStr = str.substring(0, i + 1);    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]) {      partMatch = prefix[k].length;     }    }    if (!partMatch) {     partMatch = 0;    }    return partMatch;  }  function KMP(sourceStr, searchStr) {    var sourceLength = sourceStr.length;    var searchLength = searchStr.length;    var result;    var i = 0;    var j = 0;    for (; i < sourceStr.length; i++) { //最外層循環,主串      //子循環      for (var j = 0; j < searchLength; j++) {        //如果與主串匹配        if (searchStr.charAt(j) == sourceStr.charAt(i)) {          //如果是匹配完成          if (j == searchLength - 1) {           result = i - j;           break;          } else {           //如果匹配到了,就繼續循環,i++是用來增加主串的下標位           i++;          }        } else {         if (j > 1) {          i += i - next(searchStr.slice(0,j));         } else {          //移動一位          i = (i - j)         }         break;        }      }      if (result || result == 0) {       break;      }    }    if (result || result == 0) {     return result    } else {     return -1;    }  } var s = "BBC ABCDAB ABCDABCDABDE"; var t = "ABCDAB";  show('indexOf',function() {   return s.indexOf(t)  })  show('KMP.next',function() {   return KMP(s,t)  })  function show(bf_name,fn) {   var myDate = +new Date()   var r = fn();   var div = document.createElement('div')   div.innerHTML = bf_name +'算法,搜索位置:' + r + ",耗時" + (+new Date() - myDate) + "ms";    document.getElementById("testnext").appendChild(div);  }</script></div></div>

git代碼下載: https://github.com/JsAaron/data_structure

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
91九色视频导航| 亚洲色图日韩av| 亚洲男人av在线| 成人午夜在线影院| 亚洲精品视频免费在线观看| 97成人超碰免| 欧美裸身视频免费观看| 国产精品99免视看9| 欧美片一区二区三区| 亚洲国产成人在线播放| 午夜精品美女自拍福到在线| 一区国产精品视频| 91精品在线观看视频| 久久91精品国产91久久久| 欧美猛交ⅹxxx乱大交视频| 中文字幕久久精品| 国内免费精品永久在线视频| 日韩美女在线观看一区| 久久精品99无色码中文字幕| 91av视频导航| 国产精品久久久久久久久久免费| 欧美大片大片在线播放| 欧美成年人视频| 亚洲97在线观看| 久久久91精品国产| 国内揄拍国内精品| 久久久久久久久久国产| 最近2019中文免费高清视频观看www99| 国产精品久久久久久久久久新婚| 国产精品国产三级国产专播精品人| 97在线视频国产| 亚洲精品美女久久久| 日韩精品电影网| 岛国av在线不卡| 亚洲春色另类小说| 午夜精品美女自拍福到在线| 欧美亚洲另类激情另类| 神马久久久久久| 午夜精品久久久久久久男人的天堂| 欧美激情一区二区三级高清视频| 亚洲精品mp4| 亚洲欧美一区二区精品久久久| 狠狠躁夜夜躁人人躁婷婷91| 91在线免费视频| 国产精品日韩在线一区| 国产日韩欧美成人| 国产精品扒开腿爽爽爽视频| 国产精品一区二区三区成人| 日韩电影中文字幕av| 日韩精品免费一线在线观看| 亚洲欧美日韩天堂一区二区| 浅井舞香一区二区| 亚洲欧洲美洲在线综合| 欧美电影在线观看| 91精品国产91久久久久福利| 日韩精品视频在线免费观看| 中文字幕在线看视频国产欧美| 性欧美办公室18xxxxhd| 日韩精品在线观看一区二区| 亚洲视屏在线播放| 国产一区二区日韩精品欧美精品| 日韩精品极品毛片系列视频| 亚洲电影免费观看高清完整版在线| 日韩视频在线观看免费| 色婷婷综合成人av| 日韩中文字幕亚洲| 欧美成人午夜视频| 日韩av影片在线观看| 精品国产乱码久久久久久天美| 成人在线小视频| 国产精品毛片a∨一区二区三区|国| 九色精品免费永久在线| 亚洲国产精品资源| 国产精国产精品| 日韩欧美在线免费| 欧美激情视频一区二区| 久久久久久久久久国产| 国产福利视频一区| 久久久国产精品免费| 日韩成人中文字幕| 国内精品美女av在线播放| 91在线免费观看网站| 欧美日本在线视频中文字字幕| 青青草99啪国产免费| **欧美日韩vr在线| 欧美区二区三区| 国产999精品视频| 国产日韩欧美综合| 欧美日在线观看| 亚洲精品福利在线观看| 亚洲淫片在线视频| 久久久999精品免费| 国产精品一香蕉国产线看观看| 欧美视频裸体精品| 成人黄色中文字幕| 欧美午夜激情视频| 日韩欧美国产网站| 亚洲一区二区三区在线免费观看| 国产精品91久久久| 久久香蕉精品香蕉| 久久国产精品久久久久久久久久| 国产精品极品尤物在线观看| 欧美日韩亚洲国产一区| 国产精品视频白浆免费视频| 色爱精品视频一区| 亚洲人成网7777777国产| 国产精自产拍久久久久久蜜| 亚洲精品电影久久久| 欧美精品电影在线| 精品久久久久久中文字幕一区奶水| 精品毛片网大全| 亚洲成人999| 日韩精品999| 久久人人爽人人爽人人片av高请| 一区二区三区久久精品| 日本国产欧美一区二区三区| 91社区国产高清| 91大神福利视频在线| 亚洲成人教育av| 久久久亚洲国产天美传媒修理工| 欧美日韩国产精品| 日韩欧美亚洲国产一区| 国产91露脸中文字幕在线| 国产91热爆ts人妖在线| 亚洲精品有码在线| 日韩欧美在线观看| 国产精品久久久久久久久久ktv| 欧美精品一区二区免费| 国产成人精品日本亚洲专区61| 欧美一二三视频| 97在线视频免费播放| 国产精品久久久久一区二区| 国产视频精品免费播放| 国产精品自产拍在线观看中文| 日韩欧美黄色动漫| 国产成人精品电影| 岛国精品视频在线播放| 26uuu另类亚洲欧美日本老年| 69av视频在线播放| 久久国产精品久久久久久久久久| 色偷偷88888欧美精品久久久| 久久久久久久久久久久久久久久久久av| 日韩电视剧在线观看免费网站| 久久伊人精品一区二区三区| 久久这里只有精品视频首页| 日韩精品中文字| 亚洲国产精品久久久久秋霞蜜臀| 久久网福利资源网站| 欧洲成人在线观看| 国产一区二区三区高清在线观看| 成人激情免费在线| 亚洲自拍偷拍色片视频| 精品成人在线视频| 91欧美精品午夜性色福利在线| 欧美日韩国产精品一区二区不卡中文| 午夜精品福利视频| 亚洲黄页视频免费观看| 亚洲黄色片网站| 日韩精品中文字幕在线播放| 中文字幕精品一区二区精品| 欧美日产国产成人免费图片| 成人午夜在线视频一区| 成人精品视频在线| 欧美诱惑福利视频|