第四章學習小結 串的模式匹配 解題心得體會
串的模式匹配 解題心得體會
關於串,模式匹配是其一個很重要的問題。針對這個問題,書上講了兩種模式匹配的算法,即BF算法和KMP算法,下面針對這兩種算法的實現談談我的心得。 |
一、BF算法的探索
【錯誤代碼1】
#include<iostream> #include<string.h> using namespace std; typedef struct{ char ch[1000002]; int length; }SString; void Index_BF(SString S, SString T, int pos){ int i,j; i=pos; j=1; while ( i <= S.length && j <= T.length ) { if ( S.ch[i]==T.ch[j] ) { ++i; ++j; } else { i = i-j+2; j=1; } if ( j>T.length ) { cout<< i-T.length<<endl; } else{ cout<<"0"<<endl; } } } int main() { SString S,T; char s[1000002]={0},t[1000002]={0}; cin>>s>>t; strcpy(S.ch,s); strcpy(T.ch,t); Index_BF(S, T , 1); return 0; }
分析:編譯輕松通過,但是怎麽都不能輸入進去。參考其他同學的博客,發現我的這個問題有人也遇到過,原因在於在主函數中定義了兩個長度為100W的數組,程序根本跑不動。
解決:參考博客發現有兩種途徑解決數組過大問題:①將數組定義為全局變量 ②動態分配數組
針對我這一題的具體情況,因為我是要用到strcpy函數來copy數組的內容的,所以第一種方式顯然更適合。
【錯誤代碼2】
#include<iostream> #include<string.h> using namespace std; typedef struct{ char ch[1000002]; int length; }SString; char s[1000002]={0},t[1000002]={0}; SString S,T; void Index_BF(SString S, SString T, int pos){ int i,j; i=pos; j=1; cout<<"smooth"<<endl; while ( i <= S.length && j <= T.length ) { if ( S.ch[i]==T.ch[j] ) { ++i; ++j; } else { i = i-j+2; j=1; } if ( j>T.length ) { cout<< i-T.length<<endl; } else{ cout<<"0"<<endl; } } } int main() { cin>>s>>t; cout<<"good"<<endl; strcpy(S.ch,s); cout<<"OK"<<endl; strcpy(T.ch,t); cout<<"OK"<<endl; Index_BF(S, T , 1); return 0; }
分析:這一次程序是順利運行完了,就是沒有想要的結果出來。
解決:為了查找程序在哪裏斷掉了,我在一些步驟之後設置了一些輸出,比如cout<<"good"。此法說明我的問題出在BF算法上。經過仔仔細細的檢查,發現BF算法中我打while循環的時候掉了一個括號,加上去就正常了。
接下來需要解決位置和下標的問題,我現在輸出的還是下標,不是題目要求的位置。思考過後發現解決的途徑有兩種:改數組;改算法。這裏我選擇的是改算法的方法。
【正確程序1】
#include<iostream> using namespace std; #include<string.h> //采用靜態順序存儲結構(定長) typedef struct{ char ch[1000001]; //存儲串的一維數組 int length; //串的長度 }SString; SString S,T; char s[1000001]; char t[1000001]; //BF算法 //查找 模式T 在 主串S 中第pos個字符開始第一次出現的位置,並返回 //若不存在,則返回0 (T非空,1<=pos<=S.length) int Index_BF(SString S,SString T,int pos) { int i,j; i=pos; j=0; while(i<=S.length-1 && j<=T.length-1) { if(S.ch[i]==T.ch[j]){ //從各自的第一位開始比較,如果相同,比較下一位 ++i; ++j; } else {//如果不同,主串指針回到 上次開始比較時的字符 的下一個字符, //模式回到第一個字符,重新開始比較 i=i-j+1; j=0; } } if(j>T.length-1) //匹配成功 return i-T.length+1;//主串指針位置往回退模式長度個單位,就回到了該模式在主串中第一次出現的位置 else //匹配失敗 return 0; //返回0(順序存儲的字符串是從下標為1的數組分量開始存儲的,下標為0的分量閑置不用) } //主函數 int main() { cin>>s>>t; strcpy(S.ch,s); strcpy(T.ch,t); S.length=strlen(S.ch); T.length=strlen(T.ch); cout<<Index_BF(S,T,0)<<endl;; return 0; }
現在把程序放到PTA上面跑,得到了15分,唯一錯誤的一個點就是超時。但這個是BF算法無法解決的了,所以我決定一鼓作氣把他改成KMP算法。
一、KMP算法的探索
在真正著手寫代碼前,我花了很久閱讀課本,也想了很久,終於弄明白了這個算法的核心思想。以下是我的思路:
①BF究竟是哪裏麻煩了?
設想一種情況:主串是abcab... 子串是abcac... 如果按照BF算法我們知道,一旦b和c不匹配了,接下來比較的就是主串的第二個字符b和子串的第一個字符a,不匹配時主串和子串都右移。直到子串的首位字符a與主串的a對齊之前,我們做的都是無用功。這就是從一個具體的例子看出來的麻煩之處。
②如果說KMP可以跳過這些“無用功”,那什麽情況下可以跳?這是一個特殊情況,真的能上升到一個算法的高度嗎?
為了解決我以上的疑慮,我決定用抽象的模型進行說明。
設主串Si(大家不要把它看成矽元素的元素符號啦)與模式tj失配……這句話暗含(S1S2……Si-1與t1t2……tj-1是匹配的)
這裏我們將主串Si固定死,讓他與模式中的tk匹配(因為不知道,所以設k這個未知數,也就是說這時候模式要從j跳到k)即:
Si-k+1...Si-1 =t1...tk-1
我們由第一步的匹配就已經得到:
Si-k+1...Si-1 = tj-k+1...tj-1
把這兩個式子聯立,得到:
t1...tk-1 = tj-k+1...tj-1 |
③求k
以上我們得到了一個很重要的關系式,即t1...tk-1 = tj-k+1...tj-1,它隱含了我們想要知道的k,也就是子串該跳到哪一個地方這一信息。
觀察式子,我們首先考慮一些邊邊角角的問題,比如k=1,j=1這些情況。
1)k=1 這時式子變成了t1...t0 = tj...tj-1 這種情況說明子串不符合要求,沒有那個相等的部分。順便也解決了我們第二問中的疑惑,其實KMP算法的優化是有條件的,要求子串與主串匹配的那部分有“相等的部分”。
這時候的處理辦法也只有老老實實跳到t1與Si比較。模式啊模式!你若是墮落,KMP也救不了你!
2)j=1 其實就是模式的第一位就與主串Si不匹配。那還猶豫啥?直接把模式向右移一個唄。
④怎麽用算法實現?
其實整個大框架在BF算法的基礎上來說不用怎麽改,只需要把回溯的那塊兒稍微修改一下,即改成主串i不回溯,模式跳到k位置。
但是現在說來說去我們還是只有一串關系式,還有兩個邊界的情況,k到底怎麽表示?對解決這個問題還是一臉懵逼。
閱讀課本發現他使用一個next[j]函數來表示子串下一個回溯的位置。 其實想一想next[j]僅與模式有關,和主串半毛錢關系也沒有。從已知的關系式t1...tk-1 = tj-k+1...tj-1入手,此時next[j] = k。我們求一求next[j+1]?這時候產生兩種情況需要討論:
1)若tk = tj 那麽t1...tk = tj-k+1...tj 也就是next[j+1] = k+1 , 亦即next[j+1] = next[j] +1
2)若tk!=tj 是不是有一種似曾相識的感覺?對了,這又是一次模式匹配,只不過此時的模式既充當了主串又充當模式。所以我們要把模式中的第next[j]個字符和主串中的第j個字符對齊進行比較。這種情況可以利用函數的遞歸調用,讓j=next[j]。
【正確程序2】
#include<iostream>
using namespace std;
#include<string.h>
//采用靜態順序存儲結構(定長)
typedef struct{
char ch[1000002]; //存儲串的一維數組
int length; //串的長度
}SString;
SString S,T;
char s[1000002];
char t[1000002];
int nex[1000002];
//KMP算法
//查找 模式T 在 主串S 中第pos個字符開始第一次出現的位置,並返回
//若不存在,則返回0 (T非空,1<=pos<=S.length)
int Index_KMP(SString S,SString T,int next[])
{
int i,j;
i=j=0;
while(i<=S.length-1 && j<=T.length-1)
{
if(j==-1||S.ch[i]==T.ch[j]){ //從各自的第一位開始比較,如果相同,比較下一位
++i;
++j;
}
else {
j=next[j];
}
}
if(j>T.length-1) //匹配成功
return i-T.length+1;//
else //匹配失敗
return 0;
}
void get_next(SString T,int next[]){
int i=0;
next[0]=-1;
int j=-1;
while(i<T.length-1){
if(j==-1||T.ch[i]==T.ch[j]){
++i;
++j;
next[i]=j;
}
else {
j=next[j];
}
}
}
//主函數
int main()
{
cin>>s>>t;
strcpy(S.ch,s);
strcpy(T.ch,t);
S.length=strlen(S.ch);
T.length=strlen(T.ch);
get_next(T,nex);
cout<<Index_KMP(S,T,nex)<<endl;
return 0;
}
下一周的計劃之一是抽空做做前面幾章的實踐2,我感覺代碼是越打越熟練的,所以要多加練習。而且上次小測成績不是特別理想,一個很大的問題就是我有時候太依賴書本了,所以我的第二個目標是要培養自己不看書本打代碼的能力,這也是對思維完備性的鍛煉。
第四章學習小結 串的模式匹配 解題心得體會