Thuật toán Ứng dụng — DP, String, Big Data & hơn nữa
~13 phútPattern matching & StringAll-Access

Naive string matching: vì sao O(n·m) chậm

Thuật toán so khớp chuỗi ngây thơ: trượt pattern qua text, so từng vị trí. Vì sao worst-case O(n·m) và khi nào vẫn đủ dùng trong thực tế.

TL;DR: Naive matching trượt pattern qua text từ trái sang phải, mỗi vị trí so từng ký tự. Worst-case O(n·m) xảy ra khi text lặp một ký tự (ví dụ "aaaa...a") và pattern gần như khớp hoàn toàn rồi lệch ở ký tự cuối, nên mỗi vị trí khởi đầu đều so gần hết pattern. Nhưng với văn bản tự nhiên và bảng chữ cái lớn, lệch xảy ra sớm nên thực nghiệm gần O(n). Hiểu rõ điểm lãng phí của naive (lùi con trỏ text và so lại đoạn đã biết) là chìa khoá để thấy tại sao KMP và Rabin-Karp ra đời.

Cứ hình dung bạn cần kiểm tra xem một đoạn DNA dài 10 triệu base pair có chứa chuỗi mồi (primer) dài 20 ký tự không. Cách đơn giản nhất: đặt chuỗi mồi vào vị trí 0, so từng ký tự, rồi trượt sang 1, so lại, tiếp tục cho đến cuối. Đó là naive matching: trực quan, dễ cài đặt, và trong trường hợp xấu nhất tốn tới 200 triệu phép so sánh. Bạn đã gặp nó mỗi khi gọi String.indexOf hay contains (xem bài String trong Java). Bài này phân tích cơ chế, worst-case, và điều kiện naive vẫn đủ dùng trước khi bạn đào sâu vào KMP ở bài sau.

1. Cơ chế trượt cửa sổ

Cách làm này giống việc dò một dòng trong trang sách bằng thước kẻ: đặt thước (pattern) dưới đoạn chữ, đối chiếu từng chữ; thấy lệch thì nhấc thước, dịch sang phải một chữ rồi đối chiếu lại từ chữ đầu thước.

Đời thườngThuật toán
Đoạn chữ dưới thướcCửa sổ dài m trên text
Dòng cần tìmPattern P
Thấy một chữ lệchT[i + j] ≠ P[j]
Nhấc thước, dịch một chữ, đối chiếu lại từ đầui <- i + 1, j <- 0 (con trỏ text lùi)

Naive matching duy trì một cửa sổ (window) dài m (độ dài pattern) trượt qua text dài n. Với mỗi vị trí khởi đầu i từ 0 đến n - m, thuật toán so từng ký tự P[j] với T[i + j] cho j từ 0 đến m - 1. Nếu tất cả khớp → tìm thấy. Nếu có ký tự lệch → dịch cửa sổ sang phải 1 ô và bắt đầu lại từ j = 0. Đây là hai vòng lặp lồng nhau (vòng ngoài i, vòng trong j); nếu chưa quen đếm số lần lặp của vòng lồng, ôn lại Vòng lặp lồng nhau.

Điểm mấu chốt cần nhớ: khi lệch tại j = k, ta vứt bỏ mọi thông tin về k ký tự vừa khớp: con trỏ text lùi về i + 1 và so lại từ đầu. Đó là chỗ "lãng phí" mà KMP sẽ loại bỏ.

Plain Text
function naiveSearch(T, P):
    n <- T.length
    m <- P.length
    results <- rỗng
    for i from 0 to n - m:            -- dịch cửa sổ qua text
        j <- 0
        while j < m and T[i + j] = P[j]:   -- so từng ký tự
            j <- j + 1
        if j = m:                      -- khớp toàn bộ pattern
            thêm i vào results
    return results
// Time: O(n·m) worst-case  Space: O(1) ngoài kết quả

1.1 Trace nhỏ: T = "ABCABD", P = "ABD"

Nếu chưa quen đọc bảng trace, xem lại Bảng trace trước khi đọc bảng dưới.

ijSo sánhKết quả
00T[0]='A' = P[0]='A'khớp
01T[1]='B' = P[1]='B'khớp
02T[2]='C' ≠ P[2]='D'lệch → i=1
10T[1]='B' ≠ P[0]='A'lệch → i=2
20T[2]='C' ≠ P[0]='A'lệch → i=3
30T[3]='A' = P[0]='A'khớp
31T[4]='B' = P[1]='B'khớp
32T[5]='D' = P[2]='D'khớp → tìm thấy tại 3

Trace naive trên T bằng ABCABD: tại i bằng 0 khớp hai ký tự rồi lệch, con trỏ text lùi một ô và hỏi lại từ đầu pattern

Thứ bị vứt không trừu tượng chút nào: hai xác nhận vừa mua được, T[0] là A, T[1] là B, và một bước con trỏ. KMP ở bài sau giữ đúng hai xác nhận đó.

All-Access Pass

Phần còn lại của bài dành cho All-Access Pass

Bạn vừa đọc phần xem trước. Hai bài đầu của mỗi khoá luôn đọc trọn miễn phí.

  • Đọc trọn mọi bài trong 6 khoá System Design và dữ liệu phân tán
  • AI Mentor trả lời theo toàn bộ nội dung bài
  • Trả một lần, không tự gia hạn, không tự trừ tiền

Trọn đời

1.690.000 ₫

Trả một lần, dùng mãi mãi

Gói năm

699.000 ₫

365 ngày · ≈ 58.000 ₫/tháng

Thanh toán bằng thẻ Visa/Master/JCB qua Polar. Xem bảng giá

Đã mua? Đăng nhập để đọc tiếp

Bài này có giúp bạn hiểu bản chất không?

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Thảo luận & góp ý

Hỏi đáp về bài này

Bài tiếp theo

KMP: failure function & matching O(n+m)