Thuật toán KMP có nội dung sau:
Duyệt từ trái sang phải trên S và P, mỗi lần một ký tự. Gọi con trỏ trên P là i, con trỏ trên S là j. Giả sử đã xuất hiện khúc đầu độ dài i – 1 của mẫu P và việc khớp mẫu thất bại tại vị trí j trên S, có nghĩa:
P1P2… Pi-1 Sj-i+1Sj-i+2… Sj-1 và Pi Sj
Khi đó cần phải bắt đầu đối sánh mẫu từ vị trí j – h + 1 trên S (trƣờng hợp xấu nhất h = i – 1 trong thuật toán Brute-Force). Nếu tồn tại h > 0 sao cho h – 1 ký tự đầu của mẫu khớp với h – 1 ký tự cuối của đoạn S(j – 1) hay có nghĩa đã khớp với h – 1 ký tự cuối của P(i – 1) thì ta có thể bỏ qua h – 1 phép so sánh và tiếp tục
76
so sánh ký tự Ph và Sj (Hình 4.1). Do h phụ thuộc vào i nên ký hiệu h = nex[i], i=1,.. , m.
Hình 4.1. Ý nghĩa của mảng next
Nếu Sj ≠ Ph thì phải tiếp tục lùi con trỏ trên mẫụ Để khắc phục nhƣợc điểm do tình huống này gây ra, cần cố gắng tìm h sao cho Ph có nhiều khả năng bằng Sj. Vì Sj ≠ Pi nên cần tìm h thỏa mãn Ph ≠ Pị
Trong KMP, khi i > m ta đƣợc một xuất hiện của mẫu bắt đầu từ vị trí j-m trên S. Để tìm xuất hiện tiếp theo, nếu bắt đầu đối sánh từ Sj và P1 thì có thể bỏ sót mẫu khi có mẫu xuất hiện lồng nhaụ Vì vậy, khi con trỏ trên S dừng ở vị trí j, cần trƣợt mẫu đi một số vị trí sao cho h – 1 kí tự đầu của mẫu khớp với h-1 kí tự cuối của S(j-1) hay chính là khớp với h – 1 kí tự cuối của P(m). Do đó cần mở rộng mảng next với i = m+1 (Hình 4.2). Để có next[m+1], ta tƣởng tƣợng nhƣ đã bổ sung thêm kí tự # vào cuối P, với # là một kí tự nào đó không xuất hiện trong P.
77
Nhƣ vậy, với mỗi vị trí i trên P, i = 1..m + 1, cần xác định next[i] thỏa mãn: + next[i] là số h lớn nhất sao cho h – 1 kí tự đầu của mẫu khớp với h – 1 kí tự
cuối của P(i – 1). + Pi ≠ Pnext[i].
Thuật toán 4.1.Thuật toán xây dựng mảng next
Procedure Initnext(P : char, m : integer ); Var i,j : integer;
Begin i := 1; j := 0; next[1] := 0; while i <= m do begin while j > 0 and Pi ≠ Pj do j := next[j]; i := i+1; j := j+1;
if ( i <= m) and (Pi = Pj) then next[i] := next[j] else next[i] := j;
end; end;
Cài đặt thuật toán
int *Initnext(char *pattern, int psize) {
int i = 1; int j = 0;
78 if (!next) return NULL; next[1] = 0; while ( i <= psize ) {
while ((j > 0) and pattern[i] != pattern[j]) {
j = next[j]; }
i = i + 1; j = j + 1;
if ((i <= psize) and (pattern[i] == pattern[j])) { next[i] = next[j]; } else { next[i] = j; } } printf("Bang Next \n"); printf("i : ");
for (i = 1; i <= psize+1; i++) { printf("%d ", i); }
printf("\n");
79 for (i = 1; i <= psize+1; i++)
{ printf("%d ", next[i]); }
return next; }
Thuật toán 4.2. Thuật toán KMP tìm nhiều lần lặp mẫu
Procedure KMP(S : char, n : integer, P : char, m : integer);
{Tìm mọi vị trí xuất hiện xâu mẫu P độ dài m trong xâu đích S độ dài n, đồng thời thống kê tần suất xuất hiện mẫu}
Var i, j, Counter: integer; Begin Initnext(); i := 1; j := 0; next[1] := 0; repeat while (i <= m) and ( j <= n ) do begin
while (i > 0) and (Sj ≠ Pi) do i := next[i]; i := i+1; j := j+1;
end;
if (i > m) then begin
Ghi nhận vị trí xuất hiện mẫu là j - m; counter := counter + 1;
end; i := next[i]; until j > n;
80 Ghi nhận counter;
end;
Cài đặt thuật toán
int KMP(char *target, int tsize, char *pattern, int psize) { int i; int j; int counter;
int *next = Initnext(pattern, psize); i = 1; j = 1;
counter = 0;
printf("\n Ket qua tim kiem \n"); printf("Vi tri xuat hien :"); while (j < tsize)
{ while ((i < psize) and (j < tsize))
{ while ((i > 0) and (target[j]!= pattern[i])) { i = next[i]; } i = i +1; j = j +1; } if (i >= psize) { printf(" %d,", j-psize + 1); counter = counter + 1; } i = next[i]; } return counter; }
81
Kết quả thử nghiệm
+ Trường hợp tìm thấy
Hình 4.3. Kết quả thực hiện thuật toán KMP_trường hợp tìm thấy chuỗi + Trường hợp không tìm thấy
82
Độ phức tạp của thuật toán KMP
- Pha tiền xử lý mẫu có độ phức tạp thời gian và không gian để xây dựng bảng next là O(m).
- Pha tìm kiếm có độ phức tạp thời gian xấu nhất là O(m + n).