Thuật toán BoyerMoore được đưa ra năm 1977 để kiểm tra, tìm kiếm xâu cho một văn bản T1,2,…,n có chiều dài n với mỗi Ti là một kí tự trong bảng chữ cái (alphabet) với kích thước là 256 ký tự khác nhau. và một xâu mẫu P1,2,…,m có chiều dài m (m≤n). Tìm (các) vị trí xuất hiện của xâu P (như là một xâu liên con liên tục) trong văn bản T. Như vậy, thuật toán BoyerMoore sẽ đối sánh T và P theo chiều từ phải sang trái của P. Bằng cách áp dụng thêm luật kí tự tồi (bad character rule) và luật hậu tố tốt.
Trang 1MỤC LỤC
MỤC LỤC 1
I – GIỚI THIỆU 2
II- THUẬT TOÁN BOYER MOORE 2
III- SỐ PHÉP GÁN VÀ SO SÁNH 7
IV – ĐỘ PHỨC TẠP CỦA THUẬT TOÁN 10
V- KẾT LUẬN: 13
VI – TÀI LIỆU THAM KHẢO 13
Trang 2I – GIỚI THIỆU
Thuật toán Boyer-Moore được đưa ra năm 1977 để kiểm tra, tìm kiếm xâu cho một văn bản T[1,2,…,n] có chiều dài n với mỗi T[i] là một kí tự trong bảng chữ cái (alphabet) với kích thước là 256 ký tự khác nhau và một xâu mẫu P[1,2,…,m] có chiều dài m (m≤n) Tìm (các) vị trí xuất hiện của xâu P (như là một xâu liên con liên tục) trong văn bản T
Như vậy, thuật toán Boyer-Moore sẽ đối sánh T và P theo chiều từ phải sang trái của P Bằng cách áp dụng thêm luật kí tự tồi (bad character rule) và luật hậu tố tốt
II- THUẬT TOÁN BOYER MOORE
1 Luật kí tự tồi:
Xuất hiện sự khác nhau giữa mẫu P và văn bản T: b = T [i+ j]# P[ i]=,
ta sẽ dịch sao cho có một ký tự giống b trên mẫu khớp vào vị trí đó, nếu có nhiều vị trí xuất hiện b trên mẫu ta chọn vị trí bên phải nhất Nếu không có ký
tự b nào trong mẫu ta sẽ dịch sao cho ký tự trái nhất của mẫu vào vị trí ngay sau ký tự T [i+ j]= b để đảm bảo sự ăn khớp Hai hướng tiếp cận sẽ tạo ra 2
giá trị dịch chuyển khác nhau, từ đó sẽ lựa chọn giá trị lớn hơn làm giá trị dịch chuyển
Trước hết chúng ta hãy xem xét ví dụ sau để hiểu luật này
T=FINDIFAHAYXEACKNEXDLET=FINDIFAHAYXEACKNEXDLE P=NEXDLEP
Tại bước đầu tiên (i=1)so sánh T[1,2,…,6] với P[1,2,…,6] ta thấy rằng T[6]≠P[6] (chú ý ta so sánh từ phải sang trái của P) Do T[6]∉P, ta ngay lập tức có thể dịch P lên 6 đơn vị và bắt đầu so sánh T[7,8,…,12] với P[1,2,
…,6]
Trang 3Tại bước này ta sẽ gặp vị trí không khớp T[11]≠P[5], vì T[11]=P[3], ta sẽ dịch P lên 2 đơn vị để cho T[11] thẳng hàng với P[3] và tiếp tục so sánh T[9,
…,14] với P[1,2,…,6]
Tại bước này, T[14]≠P[6] và T[14]∉P, ta dịch P lên 6 đơn vị và so sánh T[15,…,20] với P[1,2,…,6]]
Tiếp tục các bước như trong hình vẽ dưới đây, cuối cùng ta tìm được mẫu P ở vị trí i=16
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
N E X D L E
N E X D L E
N E X D L E
N E X D L E
N E X D L E
Hình 1 – Quá trình so khớp mẫu P=”NEXDLE” của thuật toán Boyer-Moore
Luật kí tự tồi: Dịch P lên một số x đơn vị sao cho kí tự tồi T[k] khớp
với kí tự P[s] là kí tự xuất hiện gần với P[m] nhất (s lớn nhất) Nếu s>js>j hoặc không tồn tại kí tự P[s] như vậy thì ta dịch P lên 1
Để thực thi luật kí tự tồi, với mỗi kí tự ∈Σ, gọi R[σ]] là vị trí xuất hiện của kí
tự σ]gần với P[m] nhất Nếu σ]∉P, R[σ]]=0 Theo quy luật kí tự tồi, mỗi bước
chúng ta sẽ dịch P một đoạn là: max(1,j−R[T[k]])
Trang 4Hình 2 – Quá trình dịch P lên một số x đơn vị
Thủ tục thuật giả mã của thuật toán BM sử dụng luật kí tự tồi như sau:
int BMmatcher(int n, int m){
badCharBackup(m);
int i = 1, j = 0, p = 0, skip = 0;
while(i <= n-m+1){
skip = 0;
j = m;
while(j >= 1 && skip == 0){
if(T[i+j-1] != P[j]){
p = T[i+j-1]-96;
skip = max(1, j - R[p]);
}
j ;
Index
x 2
x 1
x 4
x 4
x 4
x 4
Trang 5}
if(skip == 0) {
return i;
}
i = i + skip;
}
return -1;
}
void badCharBackup(int m){
int i = 0;
memset(R, 0, sizeof(R));
for(i = 1; i <= m; i++){
R[P[i] - 96] = i;
}
}
2 Luật hậu tố tốt (Good suffix rule)
Luật hậu tố tốt chúng ta phát biểu trong phần này mạnh hơn phát biểu gốc cuả thuật toán Boyer-Moore
Luật hậu tố tốt: Giả sử khi so sánh, một xâu con Tsub của văn
bản T khớp với hậu tố của mẫu P, nhưng kí tự liền kề bên trái của Tsub không
khớp với kí tự tương ứng trong P Tìm một xâu con tsub (nếu có) phải nhất (right most gần với kí tự cuối P[m] nhất) của P thỏa mãn: tsub không phải
là hậu tố của P và kí tự liền kề bên trái của tsub khác với kí tự liền kề bên trái của hậu tố Tsub của P Dịch P sao cho tsub khớp với Tsub trong T.
Nếu tsub không tồn taị, dịch P một số lượng ít nhất sao cho kí tự đầu
tiên của P vượt quá kí tự đầu tiên của Tsub và tiền tố của P khớp với hậu tố
Trang 6của Tsub Nếu không thể dịch P để thỏa mãn điều kiện đó, dịch P lên m đơn vị
Trường hợp ta tìm tất cả sự xuất hiện của P trong T, sau khi đã tìm thấy P, ta dịch P một lượng ít nhất (> 0) sao cho tiền tố của P khớp với hậu tố của P trong T Trong trường hợp không thể dịch như vậy, dịch P lên m đơn vị
Xét ví dụ sau:
T[1,2,…,30]= C G T G C C T A C T T A C T T A C T T A C T T A C G C G
A A và P[1,2,…,9]= C T T A C T T A C khi so sánh T với P, nhận thấy Tsub={TAC}=T[7,8,9], và cũng tìm được Tsub=(TAC)=P[3,4,5], ta có
thể dịch 4 đơn vị để khớp với Tsub
Hình 3: so sánh P với T theo luật Good suffix rule
Thủ tục thuật giả mã của thuật toán BM sử dụng luật kí tự tồi như sau:
int fastfastBMmatcher(int n, int m){
computeLL(m, LL, Zbar);
computeSL(m, SL, Zbar);
while(i <= n-m+1){
Trang 7skip = 0;
j = m;
while(j >= 1 && skip == 0){
if(T[i+j-1] != P[j]){
p = T[i+j-1]-96;
skip = max(1, j - R[p]);
if(j!=m){
if(LL[j+1] !=0){
skip = max(skip, m - LL[j+1]); } else {
skip = max(skip, m - SL[j+1]); }
}
}
j ;
}
if(skip == 0) return i;
i = i + skip;
}
return -1;
}
void computeLL(int m, int *LL, int *Zbar){
int i = 0, j = 0;
char Prev[m+1];
int Zrev[m+1];
for(i = m; i >= 1; i ){
Prev[m-i+1] = P[i];
}
compute_Z_function(Prev, Zrev, m);
Trang 8for(i = 1; i<= m-1; i++){
Zbar[i] = Zrev[m-i+1];
}
for(j = 1; j <= m-1; j++){
i = m - Zbar[j]+1;
LL[i] = j;
}
}
void computeSL(int m, int *SL, int *Zbar){
int i = 1,j = 0;
for(i = 1; i <= m-1; i++){
if(Zbar[i] == i){
SL[m-i+1] = i;
j = i;
} else {
SL[m-i+1] = j;
}
}}
III- SỐ PHÉP GÁN VÀ SO SÁNH
1 Số phép gán
50 113.46 102.25 88.68 82.27 89.72 87.62 102.93 81.98 92.28 98.89
100 100.78 101.11 89.66 83.91 103.99 89.13 101.59 93.30 85.16 93.20
150 102.42 83.28 81.29 87.64 81.33 77.24 79.77 91.12 77.11 73.93
200 89.21 72.92 88.50 88.49 78.86 81.59 70.83 82.85 83.40 79.98
250 46.86 56.74 61.94 58.62 57.62 57.69 59.04 52.55 57.71 59.91
300 70.17 45.83 43.98 55.88 49.93 45.63 49.21 54.75 45.51 50.93
350 29.74 48.35 52.58 55.27 37.43 46.14 49.57 44.64 42.27 46.96
400 49.45 38.34 37.58 29.29 37.24 34.17 38.65 33.43 40.20 34.79
450 45.15 35.15 37.32 34.45 34.95 33.78 34.66 31.41 31.28 35.47
500 19.42 35.95 26.88 34.35 28.60 32.25 36.98 30.35 30.04 31.44
550 22.00 29.21 23.29 29.83 26.24 23.60 23.97 23.91 24.24 23.81
600 21.35 23.71 15.67 21.23 23.26 20.44 21.26 20.53 21.30 21.12
Trang 9650 14.62 19.93 16.53 20.20 19.26 17.68 17.28 16.74 17.88 17.18
700 20.25 19.13 16.39 19.04 17.31 19.66 19.47 19.06 15.90 18.43
750 15.96 14.85 13.81 14.64 16.13 14.42 15.83 17.53 15.67 15.68
800 13.54 16.57 16.66 15.07 15.41 15.04 16.55 15.56 15.80 16.05
850 17.67 14.15 14.41 15.74 12.79 13.71 14.11 15.30 15.55 15.80
900 15.17 14.59 12.76 14.40 13.88 13.18 15.99 15.78 14.21 14.64
950 14.39 14.72 16.14 13.91 13.69 14.51 14.50 13.66 14.29 14.76
1000 16.71 13.40 14.83 14.65 12.92 14.32 13.23 13.41 13.76 13.78
b Số phép so sánh
50 223.10 201.00 173.85 160.96 175.73 171.63 202.14 160.49 180.96 194.17
100 198.21 198.77 175.76 164.35 204.45 174.77 199.60 183.10 166.82 182.89
150 201.31 162.97 159.14 171.69 159.13 151.02 156.06 178.63 150.75 144.45
200 174.89 142.38 173.32 173.48 154.34 159.60 138.17 162.17 163.27 156.41
250 90.25 109.93 120.45 113.66 111.64 111.96 114.57 101.61 111.93 116.27
300 136.75 88.14 84.42 108.14 96.43 87.70 94.96 105.94 87.52 98.32
350 56.20 93.09 101.64 106.97 71.30 88.81 95.63 85.84 81.06 90.41
400 95.52 73.00 71.66 55.12 70.94 64.81 73.75 63.37 76.86 66.06
450 86.69 66.67 71.05 65.44 66.49 64.01 65.89 59.39 59.08 67.37
500 35.52 68.44 50.14 65.22 53.71 60.96 70.36 57.21 56.51 59.36
550 40.42 54.88 43.11 56.04 49.01 43.70 44.47 44.29 45.00 44.09
600 39.35 43.95 27.94 38.94 42.98 37.39 39.08 37.58 39.16 38.69
650 26.04 36.34 29.54 36.91 34.96 31.81 31.02 29.98 32.17 30.86
700 36.94 34.66 29.38 34.50 31.18 35.73 35.40 34.57 28.35 33.31
750 28.48 26.26 24.17 25.89 28.71 25.42 28.15 31.50 27.84 27.79
800 23.70 29.55 29.71 26.64 27.45 26.60 29.56 27.61 28.10 28.57
850 31.72 24.98 25.37 27.98 22.24 24.00 24.81 27.10 27.60 27.97
900 26.87 25.72 22.02 25.32 24.29 22.90 28.40 28.01 24.90 25.81
950 25.37 26.09 28.70 24.30 23.93 25.41 25.59 23.87 25.13 26.03
1000 29.98 23.39 26.14 25.79 22.37 25.18 23.01 23.35 24.11 24.12
V- KỲ VỌNG, PHƯƠNG SAI, ĐỘ LỆCH CHUẨN, ĐỒ THỊ
5.1 Số phép gán
n Kỳ vọng Phương sai Độ lệch chuẩn Chặn trên Chặn dưới
Trang 10400 33.315 25.74407222 5.073861668 38.388862 28.24114
Đồ thị biểu diễn
5.2 Số phép so sánh
n Kỳ vọng Phương sai Độ lệch chuẩn Chặn trên Chặn dưới
Trang 11400 64.628 102.9767067 10.1477439 74.7757439 54.480256
Đồ thị biểu diễn
IV – ĐỘ PHỨC TẠP CỦA THUẬT TOÁN
Xét tổng quát chương trình sau:
int BMmatcher(int n, int m){
int R[28];
int Zbar[MAXM];
int LL[MAXM];
int SL[MAXM];
badCharBackup(m);
Trang 12int i = 1, j = 0, p = 0, skip = 0;
computeLL(m, LL, Zbar);
computeSL(m, SL, Zbar);
while(i <= n-m+1){
skip = 0;
j = m;
while(j >= 1 && skip == 0){
if(T[i+j-1] != P[j]){
p = T[i+j-1]-96;
skip = max(1, j - R[p]);
if(j!=m){
if(LL[j+1] !=0){
skip = max(skip, m - LL[j+1
} else {
skip = max(skip, m - SL[j+1
}
}
}
j ;
}
if(skip == 0) return i;
i = i + skip;
}
return -1;
}
1 Tính toán độ phức tập của chương trình con
Thực hiện tính toán trên số phép so sánh
Trang 13void badCharBackup(int m) {
int i = 0;
for (i = 0; i < m; i++) { m+1 lần
R[P[i] - 26] = i;
}
Vậy độ phức tạp O(m+1)
2 Tính toán độ phức tạp của chương trình con
Thực hiện tính toán trên số phép so sánh
void computeLL(int m, int *LL, int *Zbar){
for(i = 1; i<= m-1; i++){ m lần
Zbar[i] = Zrev[m-i+1];
}
for(j = 1; j <= m-1; j++){ m lần
i = m - Zbar[j]+1;
LL[i] = j;
}
}
Thực hiện tính toán trên số phép so sánh
void computeSL(int m, int *SL, int *Zbar){
int i = 1,j = 0;
for(i = 1; i <= m-1; i++){ m lần
if(Zbar[i] == i){ m lần
SL[m-i+1] = i;
j = i;
} else {
SL[m-i+1] = j;
}
Trang 14}
} Vậy ta tính được độ phức tạp của chương trình trên là O(4m) 3 Tính độ phức tạp trong chương trình chính Thực hiện tính toán trên số phép so sánh BOYERMOORE (int n, int m){ while(i <= n-m+1){ n-m+2 skip = 0; j = m; while(j >= 1 && skip == 0){ m(n-m+2)+1 if(T[i+j-1] != P[j]){ m(n-m+2) p = T[i+j-1]-96; skip = max(1, j - R[p]); if(j!=m){ m(n-m+2) if(LL[j+1] !=0){ m(n-m+2) skip = max(skip, m - LL[j+1 } else { skip = max(skip, m - SL[j+1
}
}
}
j ;
}
if(skip == 0) return i; m(n-m+2)
i = i + skip;
Trang 15}
}
Vậy ta tính được độ phức tạp là O(n-m+1 +m(n-m+1)+1+4(m(n-m)))
=O(5(m(n-m)+n+1)
Tổng kết lại ta có: O (m+1) +O(4m)+ O(5(m(n-m)+n+1)
Kết luận: vậy độ phức tạp của thuật toán là O(m*n)
V- KẾT LUẬN:
Thuật toán Boyer Moore là thuật toán tìm kiếm chuỗi rất có hiệu quả trong thực tiễn, các dạng khác nhau của thuật toán này thường được cài đặt trong các chương trình soạn thảo văn bản
Các đặc điểm chính của nó: Thực hiện việc so sánh từ phải sang trái Giai đoạn tiền xử lý (preprocessing) có độ phức tạp thời gian và không gian là O(m +C) Giai đoạn tìm kiếm có độ phức tạp lớn nhất O(m*n)
Thuật toán Boyer-Moore có thể đạt tới chi phí O(n/m) là nhờ có cách dịch thứ 2 “ký tự không khớp” Cách chuyển cửa sổ khi gặp “ký tự không khớp” cài đặt vừa đơn giản lại rất hiệu quả trong các bảng chữ cái lớn nên có nhiều thuật toán khác cũng đã lợi dụng các quét mẫu từ phải sang trái để sử dụng cách dịch này
Tuy nhiên có một vài thuật toán đã cải tiến cách dịch này để đưa đến chi phí tính toán của thuật toán Boyer-Moore là tuyến tính như: BMH (BM-Horspool) [1] đưa ra năm 1980 dùng cho tập đơn mẫu CW (Commentz & Walter) [10] đưa ra năm 1979, SBMH [11] năm 2002 và YBHK [12] năm
2003, WM (Wu & Manner) năm 1994 dùng cho tập đa mẫu Với CW thì độ phức tạp thời gian trong trường hợp tốt nhất với tập p mẫu là: O(n*max{m[0], m[1], m[p-1]}) còn trong trong trường hợp tốt nhất thời gian thực thi là O(n/ min{m[0], m[1], m[p-1]}) với m[i] là độ dài của mẫu thứ i
Trang 16VI – TÀI LIỆU THAM KHẢO
[1] Boyer, Robert S., and J Strother Moore A fast string searching algorithm Communications of the ACM 20.10 (1977): 762-772