Bài toán tìm kiếm xâu mẫu
Trang 1Bài toán tìm kiếm xâu mẫu
Đề bài: Cho xâu T độ dài n (gọi là văn bản_text) Cho P độ dài m (gọi là xâu mẫu_pattern) Tìm tất cả các vị trí khớp của P trong T
Giải: Có 4 thuật toán sau:
1 Tìm kiếm trực tiếp:
Ý tưởng:Dịch từng vị trí s=0,1, n-m, với mỗi vị trí xem xâu mẫu có xuất hiện ở
vị trí đó không
Code:
void NaiveSM(char* P,int m,char* T,int n){
int i,j;
for(j=0;j<=n-m;j++){
for (i=0;i<m&&P[i]==T[i+j];i++)
if (i>=m) OUTPUT(j);
}
}
Độ phức tạp: O(nm)
2 Thuật toán Boyer-Moore:
Ý tưởng:
Hàm int Last(char c,char* P): Trả vị trí cuối cùng của c trong xâu mẫu P Nếu c không xuất hiện trong P thì giá trị trả lại là -1
VD: P= ”abcabdacgj”
Vị trí 0123456789
=> Last(‘a’,P)=6 Last(‘d’,P)=5 Last(‘p’,P)=-1
Dựa trên cơ sở hàm Last, ta sẽ xây dựng các bước nhảy để tăng tính tốc độ duyệt.Thuật toán như sau:
+ Gọi s là vị trí cần khảo sát Ban đầu s=0
P a c a b a c
T a a b a c b d c a c a b a c
s=0
+ Lặp chừng nào s<=n-m:
So sánh 2 xâu P và T, lần lượt từ vị trí cuối cùng, cho tới khi gặp các kí tự khác nhau.Gọi đó là kí tự thứ j trong xâu P, tương ứng vị trí s+j trong T: VD1:
0 1 2 3 4 5
P a c a b a c
T a a b a c b d c a c a b a c
s=0 1 2 3 4 5 6 7 8 9 10 11 12 13
Ta thấy c<>b nên j=5.Tương ứng kí tự P[5] và T[5]
VD2:
0 1 2 3 4 5
P a c a b a c
T a a b a c b d c a c a b a c
s=4 5 6 7 8 9
Trang 2j=3 Kí tự P[3] vàT[7].
Nếu j=-1 => Đây là vị trí khớp, xuất s
Sau đó dịch phải bình thường(s++)
Trái lại, gọi c=T[s+j].Xét Last(c,P):
• TH1: Last(c,P)<j Ta dịch P để vị trí Last[c,P] trùng với vị trí s+j của xâu T:
VD:
0 1 2 3 4 j=5
P: a c a b a c -> j=5 , c=’b’
T: a a b a c b d c a a c last(c,P)=3 s=0 1 2 3 4 5
Sau khi dịch:
0 1 2 3 4 5 P: a c a b a c
T: a a b a c b d c a a c s=2 3 4 5 6 7
Dễ thấy thao tác dịch là s=s+j- last(c,P)
Ở VD trên ta dịch được 2 vị trí->tốt hơn dịch tuần tự
• TH2: Last(c,P)=-1 Kí tự c không xuất hiện trong P.Dịch toàn bộ P ra sau vị trí s+j của T:
VD:
0 1 2 3 4 j=5
P: a c a b a c -> j=5 , c=’d’ T: a a b a c b d c a a c e f last(c,P)=-1 s=1 2 3 4 5 6
Sau khi dịch:
0 1 2 3 4 5 P: a c a b a c
T: a a b a c b d c a a c e f s=7 8 9 10 11 12
Dễ thấy thao tác dịch vẫn là s=s+j- last(c,P)
• TH3: Nếu Last(c,P)>j Ta chỉ dịch phải 1 vị trí (s++) VD:
0 1 2 j=3 4 5
P: a c a b a c -> j=3 , c=’c’
T: a a b c a c d c a a c last(c,P)=5 s=0 1 2 3 4 5
Sau khi dịch:
0 1 2 3 4 5 P: a c a b a c
T: a a b c a c d c a a c s=1 2 3 4 5 6
VD tổng hợp:
Trang 3Để hiểu hơn,bạn hãy thử với P= abcab và T=acbabbdababcabcabb
Giải:
0 1 j=2 3 4 P: a b c a b
T: a c b a b b d a b a b c a b c a b b s=0 1 2 3 4
0 1 2 j=3 4 P: a b c a b
T: a c b a b b d a b a b c a b c a b b s=1 2 3 4 5
0 1 2 3 j=4 P: a b c a b T: a c b a b b d a b a b c a b c a b b s=2 3 4 5 6
0 1 2 3 j=4 P: a b c a b T: a c b a b b d a b a b c a b c a b b s=7 8 9 10 11
j=-1 0 1 2 3 4 P: a b c a b T: a c b a b b d a b a b c a b c a b b s=9 10 11 12 13
->xuất s=9.
0 1 2 3 j=4 P: a b c a b
T: a c b a b b d a b a b c a b c a b b s=10 11 12 13 14
j=-1 0 1 2 3 4 P: a b c a b T: a c b a b b d a b a b c a b c a b b s=12 13 14 15 16
->xuất s=12.
0 1 2 3 4 P: a b c a b
T: a c b a b b d a b a b c a b c a b b s=13 14 15 16 17 Như vậy có 2 vị trí khớp : s=9 và s=12
Trang 4 Code:
s=0;
while (s<=n-m) {
j= m-1;
while ((j>=0)&&(T[j+s]==P[j])) j ;
if (j==0) {
OUTPUT(s);
s++;
}
else {
k=last(T[j+s],P);
s=s+ max( j-k,1);
}
}
Độ phức tạp:
Hàm Last: O(m+<kích th ước bảng chữ cái>)
Chương trình:Tình huống tồi nhất O(mn+ +<kích thước bảng chữ>)
VD: P=bam-1 T=an
Kém hiệu quả với bảng chữ nhỏ
3 Thuật toán Rabin Karp:
Ý tưởng: Chuyển đổi P và các xâu con độ dài m của T sang số nguyên (n-m+1 số) Bài toán quy về tìm 1 số trong dãy n-m+1 số đã cho
Gọi kích thước bảng chữ là k
P sẽ chuyển thành:
p= km-1P[0] + km-2P[1] + +P[m-1]
=( (P[0] * k + P[1])*k + P[2]) )*k + P[m-1] (Sơ đồ Horne)
Độ phức tạp O(m)
Với các xâu con của T, nếu tính trực tiếp như trên phải có độ phức tạp:
(n-m+1) * O(m)=O((n-m+1)m)
-> Tốn kém
Tuy nhiên, ta có thể tính số sau theo số trước:
VD: Số trước: a1a2a3a4 ->t1
Số sau : a2a3a4a5 ->t2
-> t2= (t1 % km-1)*k + a5
Cách tính này chỉ có độ phức tạp O(n):
t[0]=0; offset=1;
for (i=0;i<m-1) offset* = k; //offset = km-1 for (i=0;i<m;i++) t[0]=2*t[0]+T[i];
for(s=1;s<=n-m;s++) t[s]=(t[s-1] % offset) *k + T[s+m-1];
Tóm lại thuật toán có độ phức tạp O(m+n)
Nhược điểm:
Các số p,t có thể rất lớn ,vượt quá các kiểu dữ liệu cơ bản->Các phép toán không còn là O(1) nữa
Trang 5Khắc phục: tính toán theo modul (p,t tính theo số dư khi chia cho 1 số q nào đó).Tuy nhiên như vậy dẫn đến 1 số xâu khác nhau vẫn có thể cho các số giống nhau.Vì vậy khi tìm được số t=p, ta phải kiểm tra xem vị trí đó có thật sự là khớp hay không
Nên chọn q đủ lớn,<= Max_nguyên / k
4 Thuật toán Knuth-Morris-Pratt:
Ý tưởng:
Để dễ mô tả,ta coi các xâu đánh số từ 1
Xâu W gọi là tiền tố(prefix) của xâu X nếu X có dạng WY (Y là 1 xâu nào đó) VD: X=”qetyughjk” W=”qety”
Xâu W gọi là hậu tố(suffix) của xâu X nếu X có dạng YW (Y là 1 xâu nào đó) VD: X=”qetyughjk” W=”yughjk”
Nếu có thêm W<> X thì W gọi là prefix(hay suffic) thực sự của X
Hàm int Prefix(int q):
Hàm trả độ dài của prefix dài nhất của P[1 m] đồng thời là suffix thực sự của P[1 q]
VD: P=”abcabcd”
P=”abcabcd” -> Prefix(1)=0
P=”abcabcd” -> Prefix(2)=0
P=”abcabcd” -> Prefix(3)=0
P=”a bcabcd” -> Prefix(4)=1
P=”abcabcd” -> Prefix(5)=2
P=”abcabcd” -> Prefix(6)=3
P=”abcabcd” -> Prefix(7)=0
Ta xây dựng PI(k)=Prefix(k) với k=1->m:
+ Dễ thấy PI[1]=0
+ Giả sử đã có các PI(k) với mọi k<q
Ta sẽ tính PI(q)
VD1: P=”abcabc” q=6
P=”abcabc” -> PI(5)=2
Khi bổ sung kí tự P[3], ta thấy nó khớp với “ab” thành “abc” là suffic của xâu P[1 6]: P=”abcabc”
Vậy PI(6)=PI(5)+1=3
VD2: P=”abcababcabc” q=11
P=”abcababcabc” -> PI(10)=5
Khi bổ sung kí tự P[6], ta thấy nó ghép với “abcab” thành “abcaba” không phải là suffic của xâu P[1 11]
Nhưng xâu Prefix của “abcab” (tức “ab”) thì khớp với kí tự tiếp theo(P[3]
=”c”) tạo thành xâu “abc” chính là suffic của P[1 11]
P=”abcababcabc” -> PI(11)=3
VD3: P=”abcabcabcaa” q=11
P=” abcabcabcaa” -> PI(10)=7
Khi bổ sung kí tự P[8] ,ta thấy nó ghép với “abcabca” thành “abcabcab” không phải là suffic của xâu P[1 11]
Xét xâu Prefix của “abcabca” (tức “abca”).Nó ghép với kí tự tiếp theo(P[5]
=”b”) tạo thành xâu “abcab” vẫn không là suffic của P [1 11]
Trang 6Xét xâu tiếp Prefix của “abca” (tức “a”).Nó ghép với kí tự tiếp theo(P[2]
=”b”) tạo thành xâu “ab” vẫn không là suffic của P [1 11]
Xét xâu tiếp Prefix của “a” là “”.Nó ghép với kí tự tiếp theo(P[1] =”a”) tạo thành xâu “a” là suffic của P [1 11]
V ậy: PI(11)=1 (Bạn có thể tự kiểm tra)
VD4: P=”abcababcabd” q=11
P=”abcababcabd” -> PI(10)=5
Khi bổ sung kí tự P[6] ,ta thấy nó ghép với “abcab” thành “abcaba” không phải là suffic của xâu P[1 11]
Xét xâu Prefix của “abcab” (tức “ab”).Nó ghép với kí tự tiếp theo(P[3 ]
=”c”) tạo thành xâu “abc” vẫn không là suffic của P [1 11]
Xét xâu Prefix của “abc”(tức “”).Nó ghép với kí tự tiếp theo(P[1]=”a”) tạo thành xâu “a” vẫn không là suffic của P [1 11]
V ậy PI(11)=0
Từ đó ta có thuật toán tính Prefix:
1 PI [1]= 0 ; k=0;
2 for (q=2;q<=m;q++)
while ((k>0) && (P[k+1]<>P[q]))
k=PI[k]
if (P[k+1]== P[q]) k++
PI[q]=k;
Thuật toán: Dựa trên hàm Prefix nêu trên ta có thuật toán tìm kiếm xâu mẫu:
Ý tưởng là xác định độ dài q của xâu vừa là prefix của P,vừa là suffix của T[1 i] với i = 1->n Ta thấy rằng nếu q=m thì vị trí khớp chính là i-m+1
Cách tính q gần như cách tính Prefix
1.q=0
2.for i=1 to n
while q>0 and P[q+1]<>T[i]
q=PI[q]
if P[q+1]==T[i] then q++
if q==m then
OUTPUT(i-m+1)
q=PI[q]
VD: T=”abcabcabcaababcba” P=”abcabca”
P=”abcabca” -> PI[1]=0
P=”abcabca” -> PI[2]=0
P=”abcabca” -> PI[3]=0
P=”a bcabca” -> PI[4]=1
P=”abcabca” -> PI[5]=2
P=”abcabca” -> PI[6]=3
P=”abcabca” -> PI[7]=4
q=0
Trang 7i=1: T=”abcabcabcaababcba” P[0+1]=T[1] -> q=0+1=1
P=”abcabca”
i=2: T=”abcabcabcaababcba” P[1+1]=T[2] -> q=1+1=2
P=”abcabca”
i=3: T=”abcabcabcaababcba” P[2+1]=T[3] -> q=2+1=3
P=”abcabca”
i=4: T=”abcabcabcaababcba” P[3+1]=T[4] -> q=3+1=4
P=”abcabca”
i=5: T=”abcabcabcaababcba” P[4+1]=T[5] -> q=4+1=5
P=”abcabca”
i=6: T=”abcabcabcaababcba” P[5+1]=T[6] -> q=5+1=6
P=”abcabca”
i=7: T=”abcabcabcaababcba” P[6+1]=T[7] -> q=6+1=7 -> Xuất s=1
P=”abcabca” q=PI[7]= 4
->P=”abcabca”
i=8: T=”abc abcab caababcba” P[4+1]=T[8] -> q=4+1=5
P=”abcabca”
i=9: T=”abc abcabc aababcba” P[5+1]=T[9] -> q=5+1=6
P=”abcabca”
i=10: T=”abc abcabca ababcba” P[6+1]=T[10] -> q=6+1=7 -> Xuất s=4
P=”abcabca”
->P=”abcabca”
i=11: T=”abcabcabcaa babcba” P[4+1]<>T[11] -> q=PI[4]=1
P=”abcabca” P[1+1]<>T[11] -> q=PI[1]=0
P[0+1]=T[11] -> q=0+1=1
i=12: T=”abcabcabcaab abcba” P[1+1]=T[12] -> q=1+1=2
P=”abcabca”
i=13: T=”abcabcabcaab bcba” P[2+1]<>T[13] -> q=PI[2]=0a
P=”abcabca” P[0+1]=T[13] -> q=0+1=1
i=14: T=”abcabcabcaab cabca” P[1+1]=T[14] -> q=1+1=2ab
P=”abcabca”
i=15: T=”abcabcabcaab ba” P[2+1]=T[15] -> q=2+1=3abc
P=”abcabca”
i=16: T=”abcabcabcaababcba” P[3+1]<>T[16] -> q=PI[3]=0
P=”abcabca” P[0+1]<>T[16] -> q=0
i=17: T=”abcabcabcaababcb ” P[0+1]=T[17] -> q=0+1=1a
P=”abcabca
Độ phức tạp:O(m+n)