Thuật toán Not So Naive

Đặc điểm chính

Ý tưởng

Brute-force cổ điển so x[0] với y[j] trước; nếu khớp mới so tiếp x[1], x[2], … Nhưng trong thực tế, với bảng chữ cái không quá nhỏ, xác suất x[0] khớp ngẫu nhiên với một ký tự văn bản đã tương đối thấp — brute-force gần như luôn dừng ở phép so đầu tiên. Not So Naive khai thác một quan sát tinh tế hơn: nếu ta biết trước quan hệ giữa x[0]x[1], ta có thể chọn so sánh ký tự thứ hai của mẫu trước, giúp loại bỏ nhanh hơn các vị trí sai, đồng thời cho phép dịch chuyển 2 vị trí thay vì 1 trong nhiều trường hợp — giảm đáng kể số cửa sổ phải xét trong thực tế, dù độ phức tạp lý thuyết xấu nhất không đổi.

Cụ thể, xét quan hệ giữa hai ký tự đầu mẫu:

Điểm mấu chốt: việc kiểm tra x[1] so với y[j+1] trước tiên đóng vai trò một "bộ lọc" rẻ tiền giúp loại nhanh phần lớn các vị trí sai mà không cần bảng tiền xử lý phức tạp.

Mô tả chi tiết

Tiền xử lý chỉ gồm một phép so sánh: kiểm tra x[0] có bằng x[1] hay không, từ đó ấn định hai hằng số điều khiển dịch chuyển:

Pha tìm kiếm, với mỗi vị trí cửa sổ j:

  1. So x[1] với y[j+1].
  2. Nếu khác nhau: dịch cửa sổ đi k vị trí (không so gì thêm).
  3. Nếu giống nhau: so tiếp x[2], x[3], …, x[m-1] với phần văn bản tương ứng; nếu tất cả khớp, so nốt x[0] với y[j] — nếu khớp thì báo tìm thấy tại j. Dù kết quả thế nào, dịch cửa sổ đi vị trí.

Trường hợp m = 1 xử lý riêng: chỉ so từng ký tự văn bản với x[0].

Có thể thấy thuật toán về bản chất vẫn là brute-force nhưng đổi thứ tự và điểm khởi đầu của việc so sánh để tận dụng thông tin rẻ tiền từ hai ký tự đầu mẫu, giúp giảm đáng kể số phép so sánh trung bình trên dữ liệu thực tế mà không phải trả chi phí tiền xử lý nào đáng kể.

Cài đặt bằng C

#include <stdio.h>

void search(char *x, int m, char *y, int n) {
    int i, j, k, ell;

    if (m == 1) {
        for (j = 0; j < n; ++j)
            if (y[j] == x[0])
                printf("Tim thay tai vi tri %d\n", j);
        return;
    }

    /* Tiền xử lý: O(1) */
    if (x[0] == x[1]) { k = 2; ell = 1; }
    else              { k = 1; ell = 2; }

    /* Tìm kiếm */
    j = 0;
    while (j + m <= n) {
        if (x[1] != y[j + 1])
            j += k;
        else {
            for (i = 2; i < m && x[i] == y[j + i]; ++i);
            if (i >= m && x[0] == y[j])
                printf("Tim thay tai vi tri %d\n", j);
            j += ell;
        }
    }
}

Ví dụ minh họa

Xét x = "GCAGAGAG" (m = 8) trên y = "GCATCGCAGAGAGTATACAGTACG" (n = 24).

x[0]=G khác x[1]=C, ta có k = 1, ℓ = 2.

Vài lần thử đầu:

Nhờ so x[1] trước, thuật toán loại nhanh vị trí j = 2j = 7 chỉ bằng một phép so sánh, không cần chạm tới x[0].

Tài liệu tham khảo