利用KMP算法解決串的模式匹配問題(c++) -- 數據結構


 

 

題目:

7-1 串的模式匹配 (30 分)

給定一個主串S(長度<=10^6)和一個模式T(長度<=10^5),要求在主串S中找出與模式T相匹配的子串,返回相匹配的子串中的第一個字符在主串S中出現的位置。

輸入格式:

輸入有兩行: 第一行是主串S; 第二行是模式T.

輸出格式:

輸出相匹配的子串中的第一個字符在主串S中出現的位置。若匹配失敗,輸出0.

輸入樣例:

在這里給出一組輸入。例如:

aaaaaba
ba

輸出樣例:

在這里給出相應的輸出。例如:

6




分析:

這里就是在主串里面找是否存在和模式串相等的子串啦,
如果存在就輸出該子串在主串中第一個字符的位置,否則輸出0。

主要有兩種方法:

1.BF算法(在數據量大的時候可能會導致運行超時)

2.KMP算法

這里將采用KMP算法



代碼:

#include<iostream>
#include<string.h>
using namespace std;

/*在本題中有如例題般屈指可數的字符串,也有10^6如此龐大的數據,
為了避免不必要的空間浪費,筆者決定在輸入字符串string s后
化為字符數組 char *s[s.length()]

為此,我還特地查找了string的最大容量:
堆開得足夠大,數組的最大長度是可以不斷增大的(推測最長的長度為 2^32,也就是4G。)
但編碼時有需要注意的地方,采用明文的方式,如果超過65534個字節,可能報編譯錯誤。 
*/ 

 
char* trans(string str)
{
    int size = str.length();
    char *s;
    s= new char[size];
    strcpy(s, str.c_str());
    return s;
}

void calc_next(string pstring, int *next)
{
    next[0] = -1;//-1表示模式串開頭 
    int j = 0, k = -1;
    int p_len = pstring.length();//模式串長度 
    char *p = trans(pstring);//模式串字符數組
    
    while(j<p_len){//當j尚未指向模式串尾端時 
        if(k == -1 || p[j] == p[k]) {  
            k++;//k指前綴開始下標 
            j++;//j指后綴開始下標 
            next[j] = k;//next[]存放已匹配子串中最長前后綴長度
            //其中,next[j]表示 p[0]-p[j-1]子串中最長前后綴長度
        }else{
            k = next[k]; //k回溯到模式串開頭 
        }
    }         
}


int kmp(string sstring, string pstring)
{
    int *next = new int[pstring.length()];
    calc_next(pstring, next);//得到 next[]數組
    char *s = trans(sstring), *p = trans(pstring); //轉字符串為字符數組
    int i=0, j=0;
    int pos = 0;
    
    while( i<=sstring.length() || j<=pstring.length()){
        if( j == -1 || s[i] == p[j]){
            i++;
            j++;
        }else{
            j = next[j];
            /* 
            ①有最長前后綴時: 
              當主串和模式串在主串s[j]位置(即模式串最長后綴后一位)
            不匹配時, s[j]將和 模式串最長前綴后一位比較 
            ②冇最長前后綴時: 
              j = next[0] == -1;
            整個模式串向后移一位 
            */     
        }
        
        
        if(j == pstring.length()){//匹配成功 
            pos = i-j+1;
            break; 
        }
    } 
    
    return pos;
    
} 

int main()
{
    string sstring, pstring;//定義字符串 
    getline(cin, sstring);//輸入字符串 
    getline(cin, pstring);
    
    cout<<kmp(sstring, pstring);
    return 0;
    
}

 

 2019.04.07  22:08更新

 

 

繼續查閱資料的時候發現了一個可以優化原始KMP算法的判斷條件:

以上圖為例

我們的模式串 p[j] 是與主串中 s[i] 不匹配時才開始第一次移位,但是我們發現有一種情況:

模式串中的 p[k] == p[j] ,p[j] != s[i] ;由此我們可以知道 p[k] != s[i] ,那么這時我們需要進行第二次移位。

針對上述情況,不妨在第一次移位前增加一個判斷條件,即當  p[j] == p[k] 時令 next[j] = next[k] ,如此便可一步到位,優化算法。

 

部分代碼更改如下:

    while(j<p_len){//當j尚未指向模式串尾端時 
        if(k == -1 || p[j] == p[k]) {  
            k++;//k指前綴開始下標 
            j++;//j指后綴開始下標 
            if(p[j] == p[k]){
                next[j] = next[k];
            }else{
                next[j] = k;//next[]存放已匹配子串中最長前后綴長度
            //其中,next[j]表示 p[0]-p[j-1]子串中最長前后綴長度
            }
            
        }else{
            k = next[k]; //k回溯到模式串開頭 
        }
    }

 



編程中遇到的困難:

1.KMP算法相對於BF算法或者時其他算法來說更為抽象,沒有辦法很好地從利用該算法的目的、優勢、所需操作方法等內容正向理解。
需要從KMP核心原理入手才能夠感悟到數據結構之美。

2.值得注意的一點是(部分博客沒有說清楚)我們所求的最長前后綴是在已經匹配的模式串子串中,而非整個模式串,更非主串。



總結:

在實際的應用上,BF算法相對於KMP算法來說使用可能更為方便、簡潔,實操性更強。但是KMP算法的意義在於,這是人類第一次發現
串的模式匹配問題能夠以線性模式來簡化。在KMP的學習上,與其說是學會了一種數據結構,不妨說是與自己來了一場頭腦風暴。當然,
生命不息,進步不止,在查閱KMP資料的時候,后人對於一開始的版本也進行了更多的優化,不斷完善。


時間復雜度 空間復雜度
BF算法 O(n*m) O(1)
KMP算法 O(n+m) O(m)
注:n為主串長度,m為模式串長度。KMP算法犧牲了一定的空間換取時間上的簡化。
解析:KMP算法的時間復雜度由匹配的時間復雜度O(n)+求next數組的時間復雜度O(m)求得
空間復雜度為輔助數組next[m]求得



參考資料:

1.百度百科 https://baike.baidu.com/item/kmp%E7%AE%97%E6%B3%95/10951804?fr=aladdin

2.https://blog.csdn.net/x__1998/article/details/79951598

3.《數據結構(c語言版)》李雲清等編著




以上僅為自己的一些拙見,如有不正確的地方歡迎指出。




免責聲明!

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



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