1. 程式人生 > >資料結構之串的模式匹配(C語言實現)

資料結構之串的模式匹配(C語言實現)

一、暴力匹配演算法(BF)

BF全稱為Brute-Force,最簡單直觀的模式匹配演算法。

1.演算法思想

兩個字串進行匹配時,一個主串和一個模式串,就是按照我們最容易想到的演算法來進行匹配。用兩個變數i,j分別記錄主串和模式串的匹配位置,如果兩者在某個字元不匹配,則將記錄主串匹配位置的變數i回退到匹配前i的位置的後一個位置,將j回頭到模式串的第一個字元位置;如果i和j指向的主串和模式串字元匹配,則將兩者同時向後走一個位置,繼續比較;最後,如果j的位置已經走過了模式串的所有字元,則說明此時匹配成功。
這個演算法很簡單,也容易理解,下面直接給出程式碼。

2.程式碼實現
#include<stdio.h>
#include <string.h> int BF(const char *MainStr, const char *PatternStr) { int i = 0; //主串的匹配起始位置 int j = 0; //模式串的匹配起始位置 int iMainLen = strlen(MainStr); //主串長度 int iPatLen = strlen(PatternStr); //模式串長度 while (i < iMainLen && j < iPatLen) { if (MainStr[i] == PatternStr[j]) { ++i; //當前字元匹配成功,兩者均往後走一個位置
++j; } else { i = i - j + 1; //每次未匹配成功時i回退到原來i的後一位置 j = 0; //模式串每次未匹配成功時回退到0位置 } } if (j >= iPatLen) //條件滿足說明j走過了模式串的所有字元 { return i - j; } return -1; } int main(void) { const char *MainStr = "abcdabddabc"
; const char *PatternStr = "dabc"; int iPos = BF(MainStr, PatternStr); printf("iPos = %d\n", iPos); return 0; }

二、KMP演算法

KMP演算法的高明之處在於當主串和模式串在某個字元不匹配時,指示主串匹配位置的變數不需要回退,而直接回退指示模式串匹配位置的變數,而且該變量回退時也不需要像BF演算法中回退到起始位置,而是基於原來已匹配過的結果來回退。

關於KMP演算法推導的過程很多部落格中都有,而且很多書上也講的很清楚,大家可以參考嚴奶奶的《資料結構》,裡面有很清楚的推導,我就不寫推導過程了。
下面就我在理解KMP演算法中遇到的問題加以說明,並給出KMP演算法的實現程式碼。

問題1.next陣列的本質

找到匹配成功部分的兩個儘可能長的相等的真子串,一個子串以0下標開始,另一個真子串以j-1下標結尾。(這裡的j表示模式串的第j個位置,從0開始計數)

問題2.next[j]=k的含義

next [j] = k,代表j 之前的字串中有最大長度為k 的相同字首字尾

問題3.解釋next[0]=-1,next[1]=0

假設i指示主串的當前匹配位置,j指示模式串的當前匹配位置。從問題2可以知道next[0]=-1的含義就是當
了。但是我們知道下一次匹配的情況,那就是主串的匹配位置往後走一個(即i+1位置),繼續和模式串的0號位置匹配,所以此時應該將模式串的匹配位置也向後走一個(即從-1加1為0,從0號位置開始匹配,便於寫程式碼)。

同理next[1]=0,這個更容易理解,它表示的含義是當模式串的1號位置和主串不匹配時,模式串應該回退到0號位置進行匹配,這是理所當然的做法,也是退到無路可退。

問題4.解釋if(j==-1 || sub[j] ==s[i])中j==-1這個條件

當j為-1時,說明此時模式串的第一個字元和主串不匹配,此時模式串第一個字元應該和主串的下一個字元繼續比較,即應該要sub[0]和s[i+1]比較,這兩種狀態切換也是i++和j++的一個過程,所以可以將兩者合在一起來寫。

程式碼實現

#include<stdio.h>
#include <string.h>
#include <stdlib.h>

void GetNext(const char *PatternStr, int *next)
{
    int i = 1;
    int j = 0;
    next[0] = -1;
    next[1] = 0;
    int iPatLen = strlen(PatternStr);
    while (i < iPatLen)
    {
        if (j == -1 || PatternStr[i] == PatternStr[j])
        {
            ++i;
            ++j;
            next[i] = j;
        }
        else
        {
            j = next[j];
        }
    }
}

int KMP(const char *MainStr, const char *PatternStr)
{
    int i = 0;
    int j = 0;
    int iMainLen = strlen(MainStr);     //主串長度
    int iPatLen = strlen(PatternStr);   //模式串長度
    int *next = (int*)malloc(sizeof(int) * iPatLen);
    GetNext(PatternStr, next);
    while (i < iMainLen && j < iPatLen)
    {
        if (j == -1 || MainStr[i] == PatternStr[j])
        {
            ++i;
            ++j;
        }
        else
        {
            j = next[j];
        }
    }
    if (j >= iPatLen)
    {
        return i - j;
    }
    return -1;
}
int main(void)
{
    const char *MainStr = "abcdabddabc";
    const char *PatternStr = "dabc";
    int iPos = KMP(MainStr, PatternStr);
    printf("iPos = %d\n", iPos);
    return 0;
}
在最後給出一點提醒,大家仔細看KMP()和GetNext()這兩個函式,可以發現其實兩者很相似,想想為什麼,可以更好幫助你理解KMP演算法。