kmp算法理解與記錄

字符串匹配的暴力解法

給定字符串s和p,尋找字符串p在字符串s中出現的位置,暴力解法以下所示:算法

  • 若是當前字符匹配成功,++i;++j,繼續匹配下一字符。
  • 若是s[i]s[j]匹配失敗,令i-=(j-1),j=0,即i轉到上次首次匹配開頭字符的下一位置,j從頭開始。
int brute_match(string s, string p) {
  int slen = s.size();
  int plen = p.size();
  int i = 0, j = 0;
  while (i < slen and j < plen) {
    if (s[i] == p[j]) {
      ++i;
      ++j;
    } else {
      i -= (j - 1);
      j = 0;
    }
  }
  if (j == plen)
    return i - j;
  return -1;
}

若是有一種算法可以不讓i回退,只須要移動j,那麼咱們的算法將會大爲簡化。數組

kmp算法

在kmp算法中,引入了next數組,表示當前字符以前的子字符串中具備多大長度的相同的前綴後綴,請注意,next字符串的內容是和須要匹配的字符串p相關的code

在brute-force算法的基礎上改進的kmp算法以下所示:blog

int kmp_match(string s, string p,const vector<int> &next) {
  int slen = s.size();
  int plen = p.size();
  int i = 0, j = 0;
  while (i < slen and j < plen) {
    if (j == -1 or s[i] == p[j]) {
      ++i;
      ++j;
    } else {
      j = next[j];
    }
  }
  if (j == plen)
    return i - j;
  return -1;
}

next數組的求法

首先,咱們思考一下如何計算給定字符串的最長相同前綴和後綴的長度。
  1. 設最長相同前綴後綴爲str
  2. 設數組len中每個位置和字符串s一對應,表示字符串截止到當前位置最長相同前綴後綴的長度。
  3. k表示當前位置及以前的字符串的最長相同前綴後綴的長度。s[k]表示原字符串s最長前綴後綴str以後緊跟的那個字符。
  4. 咱們從第2個字符開始尋找最長前綴後綴,若是s[i]!=s[k],表明字符i沒法進一步與字符j匹配,最長相同前綴後綴不可能在上一次匹配的基礎之上進一步增加。若是以前可以匹配的最長相同前綴後綴長度大於0,咱們不斷嘗試在上一次的基礎之上下降標準,匹配更小長度的最長前綴後綴。
  5. 關鍵點在於,如何選擇下降標準後的須要匹配的相同前綴後綴?這裏咱們將k調整爲k=len[k-1],緣由稍後詳述
  6. 若最終通過調整以後,s[i]=s[j],這表示最長相同前綴與後綴可以在長度爲k的最長相同前綴後綴的基礎之上,再增加一個字符,即len[i]=++k。不然說明當前位置沒有相同前綴後綴,記len[i]=0

只有長度大於1的字符串纔有最長前綴與後綴,最長前綴不包括最後一個字符,最長後綴不包括第一個字符。字符串

k=len[k-1]緣由講解

假設當前k指向字符串中f對應的位置,在e以前咱們匹配到的最長相同前綴後綴爲abeab,咱們發現ef不能匹配,咱們須要下降標準,嘗試匹配更短長度的最長前綴後綴,應該匹配多長的呢?string

咱們發現圖中下劃線部分的字符串str徹底相同,咱們要找的長度縮減的最長相同前綴後綴長度不能超過str的長度,並且要保證這一字符串(設爲str_new)知足條件:是相同的前綴後綴。即圖中最前面和最後面標記的字符串str_new必須徹底同樣。根據對稱性,這等價於在靠後的str中尋找最長前綴後綴。並且由於k比較的時候,k實際指向前面str的末尾下一位置,因此咱們有:k=len[k-1]基礎

void calculate_length(string s, vector<int> &len) {
  len.resize(s.length());
  len[0] = 0;
  int k = 0;
  for (int i = 1; i < s.length(); ++i) {
    while (s[i] != s[k] and k > 0) {
      k = len[k - 1];
    }
    if (s[i] == s[k]) {
      len[i] = ++k;
    } else {
      len[i] = 0;
    }
  }
}
next數組與最長前綴後綴

next數組,表示當前字符以前的子字符串中具備多大長度的相同的前綴後綴,也就是說next 數組至關於「最大長度值」 總體向右移動一位,而後初始值賦爲-1im

其示例代碼以下所示,由於和求最長前綴後綴的代碼相似,故再也不追究。next

void calculate_next(string s,vector<int>&next){
  next.resize(s.length());
  int len = s.length();
  next[0] = -1;
  int k = -1;
  int j = 0;
  while(j<len-1){
    if(k == -1 or s[j] ==s[k]){
      ++k;
      ++j;
      next[j] = k;
    }
    else{
      k = next[k];
    }
  }
}
相關文章
相關標籤/搜索