Thuật toán Boyer-Moore

Một phần của tài liệu Đánh giá và thu thập thông tin tự động trên internet sử dụng dịch vụ tìm kiếm (Trang 49 - 54)

Thuật toán Boyer Moore là thuật toán có 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.

Khác với thuật toán Knuth-Morris-Pratt (KMP), thuật toán Boyer-Moore kiểm tra các ký tự của mẫu từ phải sang trái và khi phát hiện sự khác nhau đầu tiên thuật toán sẽ tiến hành dịch cửa sổ đi Trong thuật toán này có hai cách dịch của sổ:

Cách thứ 1: gần giống nhƣ cách dịch trong thuật toán KMP, dịch sao cho những

phần đã so sánh trong lần trƣớc khớp với những phần giống nó trong lần sau.

Trong lần thử tại vị trí j, khi so sánh đến ký tự i trên mẫu thì phát hiện ra sự khác nhau, lúc đó x[i+1…m]=y[i+j...j+m-1]=u và a=x[i]  y[i+j-1]=b khi đó thuật toán sẽ dịch cửa sổ sao cho đoạn u=y[i+j…j+m-1] giống với một đoạn mới trên mẫu (trong các phép dịch ta chọn phép dịch nhỏ nhất).

Nếu không có một đoạn nguyên vẹn của u xuất hiện lại trong x, ta sẽ chọn sao cho phần đôi dài nhất của u xuất hiện trở lại ở đầu mẫu.

Cách thứ 2: Coi ký tự đầu tiên không khớp trên văn bản là b=y[i+j-1] ta sẽ

dịch sao cho có một ký tự giống b trên xâu mẫu khớp vào vị trí đó ( nếu có nhiều vị trí xuất hiện b trên xâu mẫu ta chọn vị trí phải nhất)

Nếu không có ký tự b nào xuất hiện trên mẫu ta sẽ dịch cửa sổ sao cho ký tự trái nhất của cửa sổ vào vị trí ngay sau ký tự y[i+j-1]=b để đảm bảo sự ăn khớp Trong hai cách dịch thuật toán sẽ chọn cách dịch có lợi nhất.

Code:

int BoyerMoore(char *source, char *find) {

int skip[MAX], i = 0, len, j=-1, lensource;

len = strlen(find);

lensource = strlen(source); for (i=0; i<MAX; i++)

skip[i] = len-1; for (i=0; i<len; i++) if (skip[find[i]] == len-1) skip[find[i]] = len-i-1; i = j = len-1; do { if (source[i] == find[j]) { i--; j--; } else { if (len-j+1 > skip[source[i]]) i += len-j+1; else i += skip[source[i]]; j = len-1; }

} while (j>0 && i<lensource);

if (j<=0) return i; else return -1; }

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 chi phí thuật toán của Boyer-Moore là O(m*n) vì cách dịch thứ

nhất của thuật toán này không phân tích triệt để các thông tin của những lần thử trƣớc, những đoạn đã so sánh rồi vẫn có thể bị so sánh lại. 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.

So khớp chuỗi (String Matchching) Ý nghĩa của ký tự đại diện (wildcard)

Có hai ký tự wildcard thông thƣờng là ? và * . • Dấu hỏi đại diện cho một ký tự duy nhất bất kỳ.

• Dấu sao (*) đại diện cho nhiều ký tự bất kỳ hoặc không có ký tự nào cả. Bạn hãy quan sát vài ví dụ dƣới đây:

Mẫu Chuỗi so sánh Kết quả w?ldcard wildcard khớp w?ldcard waldcard khớp

w*ldcard wldcard khớp (không có ký tự) w*ldcard willdcard khớp (có hai ký tự) w*ldcard* wldcards khớp

Thuật toán

Việc xử lý dấu hỏi khá đơn giản. Nếu không có dấu * trong chuỗi mẫu, ta chỉ cần duyệt lần lƣợt qua từng cặp ký tự trong chuỗi mẫu và chuỗi cần so sánh. Nếu gặp ký tự ? bên chuỗi mẫu thì ta luôn luôn bỏ qua (coi nhƣ cặp ký tự ở hai chuỗi là khớp). Ta chỉ cần lƣu ý là hai chuỗi phải có cùng chiều dài.

Code:

int Match(char *pattern, char *str) {

int i; int j;

for (i=0, j=0;( i<strlen(pattern) ) && ( j <strlen(str)) ;i++,j++) { if ( pattern[i] == '?') continue; else if (pattern[i] != str[j]) return 0; } (adsbygoogle = window.adsbygoogle || []).push({});

if ( (i==strlen(pattern)) && (j == strlen(str)) ) return 1;

else return 0; }

Bƣớc tiếp theo là xử lý trƣờng hợp dấu *. Vì dấu sao đại diện cho nhiều hoặc không ký tự nào nên việc so khớp có vẻ rắc rối. Tuy nhiên, nếu ta xử lý theo kiểu đệ quy thì vấn đề sẽ dễ hiểu và đơn giản hơn. Mục tiêu của đệ quy là đƣa bài toán về một bài toán cùng dạng nhƣng độ phức tạp giảm dần cho đến khi đạt đến những bài toán cơ bản, dễ giải quyết.

Việc xử lý dấu * chỉ có nghĩa khi phần đầu của chuỗi mẫu và chuỗi so sánh đã khớp với nhau, nếu không thì ta đã có thể kết luận ngay là không khớp. Do vậy để đơn giản mà không làm mất tính tổng quát, ta có thể giả sử chuỗi mẫu bắt đầu bằng ký tự *

Nhƣ vậy chuỗi mẫu sẽ gồm 2 phần: dấu * và phần còn lại. Chuỗi cần so sánh cũng sẽ có 2 phần tƣơng ứng: ký tự tại vị trí dấu * và phần còn lại.

Có 3 trƣờng hợp sau dẫn đến kết quả là chuỗi mẫu và chuỗi so sánh khớp nhau:

• Phần còn lại của chuỗi mẫu khớp với toàn bộ chuỗi so sánh (tính chất dấu * đại diện cho không có ký tự nào)

• Toàn bộ chuỗi mẫu khớp với phần còn lại của chuỗi so sánh (tính chất dấu * đại diện cho 1 hoặc nhiều ký tự)

Hai trƣờng hợp (b) và (c) sẽ dẫn đến việc thực hiện lại bài toán so khớp giữa chuỗi con của chuỗi mẫu và chuỗi con của chuỗi so sánh. Bạn dễ dàng thấy đƣợc tính dừng của phép đệ quy này vì mỗi lần đệ quy ta đều giảm chiều dài của chuỗi mẫu hoặc chuỗi so sánh đi một ký tự.

Nếu cả 3 trƣờng hợp trên đều không thỏa thì chuỗi mẫu và chuỗi so sánh không khớp nhau. Bây giờ ta chỉ cần sửa lại chƣơng trình ban đầu để đƣa lời gọi đệ quy vào là xong.

Code:

int Match(char *pattern, char *str) {

int i; int j;

for (i=0, j=0;( i<strlen(pattern) ) && ( j <strlen(str)) ;i++,j++) { if ( pattern[i] == '?') continue; else if (pattern[i] == '*') { if (i==strlen(pattern)-1) return 1;

else if (Match(&pattern[i+1], &str[i])) return 1; else if (Match(&pattern[i], &str[j+1])) return 1; else return 0;

}

else if (pattern[i] != str[j]) return 0;

}

if ( (i==strlen(pattern)) && (j == strlen(str)) ) return 1;

else return 0; }

Một phần của tài liệu Đánh giá và thu thập thông tin tự động trên internet sử dụng dịch vụ tìm kiếm (Trang 49 - 54)