資料結構之串的模式匹配(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演算法。