【數據結構】KMP算法


從暴力匹配到快速匹配(KMP算法)

學習kmp算法前,首先要先了解什么是kmp算法,kmp算法具體優點是什么,kmp的主要應用方向在哪。
然后才是,代碼實現

帶着以上問題,我們來一步一步學習kmp算法。
問題: 給一串字符,讓你從中找出與模式串相同的一段子串

例如:給這么一段字符
(主串:ABBABBABABAAABABAAA)
(模式串:ABAABABAA)
要求從主串中找出與模式串相同的一段子串

那么,一般方法,就是暴力匹配

暴力匹配(BF算法)

直接給圖
image

很直觀,也是多數人直接想到的方法,就是模式串中每一個字符和主串一一進行比較,如果出現不同的,模式串后移一位,重復上述操作,一直找到與模式串相同的一段子串,這種方法稱為暴力匹配。
優點:方法簡單,暴力。
缺點:平均時間復雜度O ( n * m )

思考:能否利用已部分匹配過的信息而加快模式串的滑動速度?
答案:KMP算法

快速匹配(KMP算法)

還是先給圖
image
從上圖中,箭頭指向失配元素,小方框內是最大前綴和最大后綴,大方框中是要已經匹配的字符串,我們就是從這串字符做文章的
(重點!!已匹配的字符串 下文就此展開)
然后將最大前綴移動到最大后綴的位置,就完成了一次最優”滑動

那么什么是最大前綴和最大后綴呢?

最大前綴&最大后綴(如果了解,可跳過)


給圖!
紅色下划線:最大前綴
黑色下划線:最大后綴

image


滑動大小取決於模式串

當模式串出現失配,以此處向前從已匹配的字符串中找最大前綴和最大后綴,因為前面字符和主串字符一致,而滑動是將最大前綴移動到最大后綴的位置上最大前綴和最大后綴是從子串中找,
既然如此,

當模式串第m個字符失配,那么就從模式串串1---m中找最大前綴和最大后綴,將最大前綴移動到最大后綴的位置上
以上(從那么開始)操作,和主串無關,既

給圖
image

KMP算法代碼實現

由以上的理解,你大概知道了kmp算法的基本原理。那么,如何實現呢?
給代碼前,了解一下“next[]”數組

next[]數組(如已了解,可跳過)


當光標指向失配字符時,我們需要移動模式串,那么移動后,我們的光標應該指向什么位置?

image

答:光標指向最大前綴長度+1的位置上
可見,模式串每個位置,都能對應這么一個數:以此向前已匹配的字符串的最大前綴長度+1,來作為光標的指示標志

如果將這么一組數存儲到數組中,便可以實現模式串任意一個位置失配,滑動的距離(既光標重新指向的字符)
next[]數組就是存儲這么一組能夠決定光標重新指向(滑動的距離)的一組數據


行文至此,咱們全面了解了暴力匹配的思路、KMP算法的原理、流程、流程之間的內在邏輯聯系,以及next 數組
那么,我們需要着手解決代碼實現

直接上代碼

首先實現getnext

void GetNext(char* p,int next[])  
{  
    int pLen = strlen(p);  
    next[0] = -1;  
    int k = -1;  
    int j = 0;  
    while (j < pLen - 1)  
    {  
        //p[k]表示前綴,p[j]表示后綴  
        if (k == -1 || p[j] == p[k])   
        {  
            ++k;  
            ++j;  
            next[j] = k;  
        }  
        else   
        {  
            k = next[k];  
        }  
    }  
}

到這,你可能又迷了。這代碼啥意思啊,怎么和我想象的不太一樣,別急,繼續往下看
image

從上表,你可以跟着一步一步來,就會發現其中奧秘;

接下來,給出kmp算法

int KmpSearch(char* s, char* p)  
{  
    int i = 0;  
    int j = 0;  
    int sLen = strlen(s);  
    int pLen = strlen(p);  
    while (i < sLen && j < pLen)  
    {  
        //①如果j = -1,或者當前字符匹配成功(即S[i] == P[j]),都令i++,j++      
        if (j == -1 || s[i] == p[j])  
        {  
            i++;  
            j++;  
        }  
        else  
        {  
            //②如果j != -1,且當前字符匹配失敗(即S[i] != P[j]),則令 i 不變,j = next[j]      
            //next[j]即為j所對應的next值        
            j = next[j];  
        }  
    }  
    if (j == pLen)  
        return i - j;  
    else  
        return -1;  
}  

對next的改進

先找缺陷
image
顯然,當我們上邊的算法得到的next數組應該是[ -1,0,0,1 ]

所以下一步我們應該是把j移動到第1個元素咯:

image
不難發現,這一步是完全沒有意義的。因為后面的B已經不匹配了,那前面的B也一定是不匹配的,同樣的情況其實還發生在第2個元素A上。

顯然,發生問題的原因在於t[j] == t[next[j]]。

void Getnext(int next[],String t)
{
   int j=0,k=-1;
   next[0]=-1;
   while(j<t.length-1)
   {
      if(k == -1 || t[j] == t[k])
      {
         j++;k++;
         if(t[j]==t[k])//當兩個字符相同時,就跳過
            next[j] = next[k];
         else
            next[j] = k;
      }
      else k = next[k];
   }
}

本文對b站天勤率輝
對https://blog.csdn.net/dark_cy/article/details/88698736多有借鑒

還有的是對代碼的解釋有點太少(基本沒有),原因是自己對代碼理解不太夠,半知半解。不敢亂講,原理懂了,代碼很是神奇,有點迷,希望大神能夠給一份詳細的注釋和解釋,多謝,歡迎討論。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM