Bài toán tìm kiếm xâu mẫu
Bà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 5P 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 13Ta thấy c<>b nên j=5.Tương ứng kí tự P[5] và T[5]. VD2: 0 1 2 3 4 5P 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 j=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 7Dễ 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 12Dễ 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: Để hiểu hơn,bạn hãy thử với P= abcab và T=acbabbdababcabcabbGiả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. 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-1for (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. Khắ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 / k4. 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 bca bcd” -> Prefix(4)=1 P=”ab cab cd” -> Prefix(5)=2 P=”abc abc d” -> 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=”ab cab c” -> 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=”abc abc ” Vậy PI(6)=PI(5)+1=3. VD2: P=”abcababcabc” q=11 P=”abcab abcab c” -> 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=”abc ababcabc ” -> PI(11)=3 VD3: P=”abcabcabcaa” q=11 P=” abcabca bca a” -> 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] Xé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=”abcab abcab d” -> 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=02.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]=0P=”abcabca” -> PI[2]=0P=”abcabca” -> PI[3]=0P=”a bca bca” -> PI[4]=1P=”ab cab ca” -> PI[5]=2P=”abc abc a” -> PI[6]=3P=”abca bca ” -> PI[7]=4q=0 i=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=”abcabcabca a 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=1i=12: T=”abcabcabca ab abcba” P[1+1]=T[12] -> q=1+1=2 P=”abcabca”i=13: T=”abcabcabcaab a bcba” P[2+1]<>T[13] -> q=PI[2]=0 P=”abcabca” P[0+1]=T[13] -> q=0+1=1 i=14: T=”abcabcabcaab ab cabca” P[1+1]=T[14] -> q=1+1=2 P=”abcabca”i=15: T=”abcabcabcaab abc ba” P[2+1]=T[15] -> q=2+1=3 P=”abcabca”i=16: T=”abcabcabcaababcba” P[3+1]<>T[16] -> q=PI[3]=0 P=”abcabca” P[0+1]<>T[16] -> q=0i=17: T=”abcabcabcaababcb a ” P[0+1]=T[17] -> q=0+1=1 P=”abcabca Độ phức tạp:O(m+n) . Bà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. 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