w^k với k ≥ 2 và một tiền tố nào đó là lũy thừa của ký tự đầu), thuật toán tận dụng thông tin về loạt ký tự đầu đã khớp ở lần thử trước để không so lại.Colussi đã giảm số phép so sánh bằng cách khéo léo chọn thứ tự so các vị trí, nhưng vẫn "quên" một dạng thông tin: khi mẫu bắt đầu bằng một loạt ký tự giống nhau (ví dụ mẫu aac… bắt đầu bằng hai chữ a), sau một lần thử thành công một phần, ta thường đã biết chắc một đoạn văn bản gồm toàn ký tự x[0]. Trong lần thử kế tiếp, thay vì so lại từng ký tự của đoạn đó, ta có thể "quét nhanh" để kéo dài loạt ký tự và bỏ qua các so sánh thừa.
Đặt ℓ là chỉ số sao cho x[0] = x[1] = … = x[ℓ] nhưng x[ℓ+1] ≠ x[0] — nghĩa là mẫu mở đầu bằng loạt (ℓ+1) ký tự x[0]. Galil-Giancarlo phân biệt hai chế độ:
y[j..last] gồm toàn ký tự x[0]. Khi đó ta kéo dài loạt ký tự đó về phía phải mà không so lại phần đã biết, rồi căn chỉnh cửa sổ dựa trên độ dài loạt và ký tự đầu tiên khác x[0].Với mẫu là lũy thừa của một ký tự (x = a^m), bài toán suy biến thành đếm loạt ký tự và được xử lý riêng bằng một vòng lặp đơn giản.
Tiền xử lý dùng lại toàn bộ bảng của Colussi (h, shift, nxt, và nd), cộng thêm việc tính ℓ.
Trong pha tìm kiếm ta duy trì thêm cờ heavy và biến last:
last = chỉ số văn bản lớn nhất đã khớp chắc chắn (như Colussi).heavy = đúng khi lần thử trước kết thúc ở "pha hai" và cửa sổ mới vẫn nằm trong vùng [j, last] đã khớp, tức ta đang có sẵn một loạt ký tự x[0].Khi heavy bật và ta đang ở đầu mẫu (i = 0):
y[j..last] toàn là x[0], ta đặt độ dài loạt k = last − j + 1 rồi tiếp tục quét y[j+k], y[j+k+1], … chừng nào còn bằng x[0].k ≤ ℓ) hoặc ký tự dừng y[j+k] khác x[ℓ+1]: không thể khớp bắt đầu trong loạt này, ta nhảy cửa sổ tới j + k + 1.ℓ+1 ký tự x[0] rồi x[ℓ+1]) khớp với cuối loạt, đặt i = 1 và tiếp tục ở chế độ nhẹ.Khi heavy tắt, mỗi lần thử diễn ra hệt như Colussi; sau đó ta tính lại heavy dựa trên việc lần thử vừa rồi có bước sang pha hai và cửa sổ mới có còn nằm trong vùng đã khớp không.
#include <stdio.h>
#include <stdlib.h>
/* dùng lại preColussi ở bài Colussi */
int preColussi(char *x, int m, int h[], int nxt[], int shift[]);
void search(char *x, int m, char *y, int n) {
int i, j, k, ell, last, nd, heavy, phase2;
int *h, *nxt, *shift;
/* độ dài loạt ký tự đầu */
for (ell = 0; ell + 1 < m && x[ell + 1] == x[0]; ++ell);
if (ell == m - 1) { /* x = a^m : đếm loạt trực tiếp */
k = 0;
for (j = 0; j < n; ++j) {
if (y[j] == x[0]) {
++k;
if (k >= m) printf("Tim thay tai vi tri %d\n", j - m + 1);
} else k = 0;
}
return;
}
h = (int *) malloc((m + 1) * sizeof(int));
nxt = (int *) malloc((m + 1) * sizeof(int));
shift = (int *) malloc((m + 1) * sizeof(int));
nd = preColussi(x, m, h, nxt, shift);
i = j = heavy = 0;
last = -1;
while (j + m <= n) {
if (heavy && i == 0) {
/* biết y[j..last] = x[0] lặp lại; kéo dài loạt */
k = last - j + 1;
while (j + k < n && y[j + k] == x[0]) ++k;
if (j + k >= n) break;
if (k <= ell || y[j + k] != x[ell + 1]) {
j += k + 1;
last = j - 1;
i = 0;
} else {
j = j + k - ell - 1;
last = j + ell + 1;
i = 1;
}
heavy = 0;
} else {
while (i < m && last < j + h[i] && x[h[i]] == y[j + h[i]])
++i;
if (i >= m || last >= j + h[i]) {
printf("Tim thay tai vi tri %d\n", j);
i = m;
}
phase2 = (i > nd);
if (phase2) last = j + m - 1;
j += shift[i];
i = nxt[i];
heavy = (phase2 && j <= last);
}
}
free(h); free(nxt); free(shift);
}
Với mẫu x = "GCAGAGAG", do x[0]=G khác ngay x[1]=C nên ℓ = 0: mẫu không mở đầu bằng loạt ký tự lặp, cờ heavy không mang lại lợi ích và thuật toán chạy y hệt Colussi.
Để thấy rõ chế độ "nặng", ta lấy mẫu x = "aacaa" (m = 5, ℓ = 1, tức mở đầu bằng hai chữ a rồi x[2]=c) trên văn bản y = "aaaaacaacaaa" (n = 12).
j = 0, 1, 2: so x[2]=c với y[2], y[3], y[4] đều là a → lệch, trượt 1.j = 3: mọi vị trí khớp → tìm thấy tại vị trí 3; lần thử này sang pha hai nên đặt last = 7, trượt tới j = 6, bật heavy.j = 6 (chế độ nặng): đã biết y[6..7] = "aa", kéo dài loạt tới y[8]=c. Vì k = 2 > ℓ và y[8] = c = x[ℓ+1], ta căn chỉnh và đặt i = 1.Nhờ khai thác loạt a đã biết, thuật toán tránh so lại những ký tự đã xác nhận, giữ tổng số so sánh không vượt 4n/3.