字符串匹配算法綜述


字符串匹配算法綜述:BF、RK、KMP、BM、Sunday

寫的好棒!!!%%%粘來咯...

字符串匹配算法,是在實際工程中經常遇到的問題,也是各大公司筆試面試的常考題目。此算法通常輸入為原字符串(string)和子串(pattern),要求返回子串在原字符串中首次出現的位置。比如原字符串為“ABCDEFG”,子串為“DEF”,則算法返回3。常見的算法包括:BF(Brute Force,暴力檢索)、RK(Robin-Karp,哈希檢索)、KMP(教科書上最常見算法)、BM(Boyer Moore)、Sunday等,下面詳細介紹。

 

1 BF算法: 
暴力檢索法是最好想到的算法,也最好實現,在情況簡單的情況下可以直接使用: 
這里寫圖片描述
首先將原字符串和子串左端對齊,逐一比較;如果第一個字符不能匹配,則子串向后移動一位繼續比較;如果第一個字符匹配,則繼續比較后續字符,直至全部匹配。 
時間復雜度:O(MN)

 

2 RK算法: 
RK算法是對BF算法的一個改進:在BF算法中,每一個字符都需要進行比較,並且當我們發現首字符匹配時仍然需要比較剩余的所有字符。而在RK算法中,就嘗試只進行一次比較來判定兩者是否相等。 
RK算法也可以進行多模式匹配,在論文查重等實際應用中一般都是使用此算法。 
這里寫圖片描述 
首先計算子串的HASH值,之后分別取原字符串中子串長度的字符串計算HASH值,比較兩者是否相等:如果HASH值不同,則兩者必定不匹配,如果相同,由於哈希沖突存在,也需要按照BF算法再次判定。 
按照此例子,首先計算子串“DEF”HASH值為Hd,之后從原字符串中依次取長度為3的字符串“ABC”、“BCD”、“CDE”、“DEF”計算HASH值,分別為Ha、Hb、Hc、Hd,當Hd相等時,仍然要比較一次子串“DEF”和原字符串“DEF”是否一致。 
時間復雜度:O(MN)(實際應用中往往較快,期望時間為O(M+N))

 

3 KMP算法: 
字符串匹配最經典算法之一,各大教科書上的看家絕學,曾被投票選為當今世界最偉大的十大算法之一;但是晦澀難懂,並且十分難以實現,希望我下面的講解能讓你理解這個算法。 
KMP算法在開始的時候,也是將原字符串和子串左端對齊,逐一比較,但是當出現不匹配的字符時,KMP算法不是向BF算法那樣向后移動一位,而是按照事先計算好的“部分匹配表”中記載的位數來移動,節省了大量時間。這里我借用一下阮一峰大神的例子來講解: 
這里寫圖片描述 
首先,原字符串和子串左端對齊,比較第一個字符,發現不相等,子串向后移動,直到子串的第一個字符能和原字符串匹配。 
這里寫圖片描述 
當A匹配上之后,接着匹配后續的字符,直至原字符串和子串出現不相等的字符為止。 
這里寫圖片描述 
此時如果按照BF算法計算,是將子串整體向后移動一位接着從頭比較;按照KMP算法的思想,既然已經比較過了“ABCDAB”,就要利用這個信息;所以針對子串,計算出了“部分匹配表”如下(具體如何計算后面會說,這個先介紹整個流程): 
這里寫圖片描述 
剛才已經匹配的位數為6,最后一個匹配的字符為“B”,查表得知“B”對應的部分匹配值為2,那么移動的位數按照如下公式計算: 
移動位數 = 已匹配的位數 - 最后一個匹配字符的部分匹配值 
那么6 - 2 = 4,子串向后移動4位,到下面這張圖: 
這里寫圖片描述 
因為空格和“C”不匹配,已匹配位數為2,“B”對應部分匹配值為0,所以子串向后移動2-0=2位。 
這里寫圖片描述 
空格和“A”不匹配,已匹配位數為0,子串向后移動一位。 
這里寫圖片描述 
逐個比較,直到發現“C”與“D”不匹配,已匹配位數為6,“B”對應部分匹配值為2,6-2=4,子串向后移動4位。 
這里寫圖片描述 
逐個比較,直到全部匹配,返回結果。 
下面說明一下“部分匹配表”如何計算,“部分匹配值”是指字符串前綴和后綴所共有元素的長度。前綴是指除最后一個字符外,一個字符串全部頭部組合;后綴是指除第一個字符外,一個字符串全部尾部組合。以”ABCDABD”為例: 
“AB”的前綴為[A],后綴為[B],共有元素的長度為0; 
“ABC”的前綴為[A, AB],后綴為[BC, C],共有元素的長度0; 
“ABCD”的前綴為[A, AB, ABC],后綴為[BCD, CD, D],共有元素的長度為0; 
“ABCDA”的前綴為[A, AB, ABC, ABCD],后綴為[BCDA, CDA, DA, A],共有元素為”A”,長度為1; 
“ABCDAB”的前綴為[A, AB, ABC, ABCD, ABCDA],后綴為[BCDAB, CDAB, DAB, AB, B],共有元素為”AB”,長度為2; 
“ABCDABD”的前綴為[A, AB, ABC, ABCD, ABCDA, ABCDAB],后綴為[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的長度為0。 
在計算“部分匹配表”時,一般使用DP(動態規划)算法來計算(表示為next數組)://這里我沒看懂,理論上不用DP直接搜也行啊

 

        int* next = new int[needle.length()]; next[0] = 0; int k = 0; for (int i = 1; i < needle.length(); i++) { while (k > 0 && needle[i] != needle[k]) { k = next[k - 1]; } if (needle[i] == needle[k]) { k++; } next[i] = k; }

 

 

時間復雜度:O(N)

 

4 BM算法: 
在本科的時候,我一直認為KMP算法是最好的字符串匹配算法,直到后來我遇到了BM算法。BM算法的執行效率要比KMP算法快3-5倍左右,並且十分容易理解。各種記事本的“查找”功能(CTRL + F)一般都是采用的此算法。 
網上所有講述這個算法的帖子都是以傳統的“好字符規則”和“壞字符規則”來講述的,但是個人感覺其實這樣不容易理解,我總結了另外一套簡單的算法規則: 
我們拿這個算法的發明人Moore教授的例子來講解: 
這里寫圖片描述 
首先,原字符串和子串左端對齊,但是從尾部開始比較,就是首先比較“S”和“E”,這是一個十分巧妙的做法,如果字符串不匹配的話,只需要這一次比較就可以確定。 
在BM算法中,當每次發現當前字符不匹配的時候,我們就需要尋找一下子串中是否有這個字符;比如當前“S”和“E”不匹配,那我們需要尋找一下子串當中是否存在“S”。發現子串當中並不存在,那我們將子串整體向后移動到原字符串中“S”的下一個位置(但是如果子串中存在原字符串當前字符腫么辦呢,我們后面再說): 
這里寫圖片描述 
我們接着從尾部開始比較,發現“P”和“E”不匹配,那我們查找一下子串當中是否存在“P”,發現存在,那我們就把子串移動到兩個“P”對齊的位置: 
這里寫圖片描述 
已然從尾部開始比較,“E”匹配,“L”匹配,“P”匹配,“M”匹配,“I”和“A”不匹配!那我們就接着尋找一下子串當前是否出現了原字符串中的字符,我們發現子串中第一個“E”和原字符串中的字符可以對應,那直接將子串移動到兩個“E”對應的位置: 
這里寫圖片描述 
接着從尾部比較,發現“P”和“E”不匹配,那么檢查一下子串當中是否出現了“P”,發現存在,那么移動子串到兩個“P”對應: 
這里寫圖片描述 
從尾部開始,逐個匹配,發現全部能匹配上,匹配成功~ 
時間復雜度:最差情況O(MN),最好情況O(N)

 

5 Sunday算法: 
后來,我又發現了一種比BM算法還要快,而且更容易理解的算法,就是這個Sunday算法: 
這里寫圖片描述 
首先原字符串和子串左端對齊,發現“T”與“E”不匹配之后,檢測原字符串中下一個字符(在這個例子中是“IS”后面的那個空格)是否在子串中出現,如果出現移動子串將兩者對齊,如果沒有出現則直接將子串移動到下一個位置。這里空格沒有在子串中出現,移動子串到空格的下一個位置“A”: 
這里寫圖片描述 
發現“A”與“E”不匹配,但是原字符串中下一個字符“E”在子串中出現了,第一個字符和最后一個字符都有出現,那么首先移動子串靠后的字符與原字符串對齊: 
這里寫圖片描述 
發現空格和“E”不匹配,原字符串中下一個字符“空格”也沒有在子串中出現,所以直接移動子串到空格的下一個字符“E”: 
這里寫圖片描述 
這樣從頭開始逐個匹配,匹配成功! 
時間復雜度:最差情況O(MN),最好情況O(N)

//實際我寫好像可以是o(M+N)啊。。

代碼粘一下:

 

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
char a[10005],b[10005];//long a>long b
int c[30];//表示b串中存在的字母;不存在則為1,存在為最靠后的此字符距離尾部加一(要跳的地方) 
int la,lb;//字符串a,b的長度 
int head;//當前搜索到的頭字符 
int main()
{
    scanf("%s",a);
    scanf("%s",b);//read in
    la=strlen(a);
    lb=strlen(b); 
    for(int i=0;i<=lb-1;i++)
        c[b[i]-'a'+1]=lb-i;//初始化c數組 
    for(int i=0;head<=la-1;)//i表示當前匹配長度 ,head指針跳到a尾時結束 
    {
        if(a[head+i]==b[i])
        {
            i++;//匹配則更新i值
            if(i==lb) //匹配到的長度等於b串長度 則成功 
            {
                printf("Yes");return 0;
            }
        }        
        else
        {
            if(c[a[head+lb]-'a'+1]!=0) head=head+c[a[head+lb]-'a'+1];//判斷是否出現
            else head=head+lb+2; //未出現,跳到下一個長度 
            i=0;//匹配值更新為0
        }         
    }
    printf("No");
    return 0;
}

 

 

 

 


免責聲明!

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



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