Levenshtein distance - Bài toán tìm kiếm chuỗi tương tự
Đặt vấn đề
Giả như người dùng muốn tìm kiếm chuỗi “command“, nhưng anh ta gõ nhầm thành “comnand“, làm cách nào hệ thống vẫn hiểu để trả về kết quả chính xác?
Khoảng cách Levenshtein
Là số bước ít nhất để biến một chuỗi \(A\) thành chuỗi \(B\) thông qua 3 phép biến đổi:
Deletion: Xóa một ký tự
Insertion: Thêm 1 ký tự
Substitution: Thay 1 ký tự bằng một ký tự khác
Ví dụ: Khoảng cách của “GILY“ và “GEELY“ là 2, vì để biến “GILY” thành “GEELY” cần có 2 bước:
Thay I thành E: GILY → GELY
Chèn thêm E vào: GELY → GEELY
Mô tả thuật toán tính khoảng cách Levenshtein
Ý tưởng chính của thuật toán là ta sẽ đi xây dựng một bảng (mảng 2 chiều) để lưu trữ số thao tác tối thiểu cần thực hiện để chuyển một đoạn của chuỗi thứ nhất thành một đoạn của chuỗi thứ hai. Bảng này được tính theo ý tưởng của thuật toán quy hoạch động (dynamic programming).
Mô tả thuật toán
Đầu vào là chuỗi \(A[0 \dots m-1]\) và \(B[0 \dots n-1]\) với \(m,n\) lần lượt là độ dài của chuỗi\(A\) và chuỗi \(B\).
Khởi tạo bảng \(D\):
Cho \(i:0 \to m:\) \(D[i][0] = i\)
Cho \(j:0 \to n:\) \(D[0][j] = j\)
Với \(D[i][j]\) là khoảng cách Levenshtein giữa:
\(A[0\dots i-1]\): chuỗi con khởi đầu từ vị trí \(0\) đến vị trí \(i-1\) có độ dài \(i\)
\(B[0 \dots j-1]\): chuỗi con khởi đầu từ vị trí \(0\) đến vị trí \(j-1\) có độ dài \(j\)
Công thức đệ quy để tính \(D[i][j]\):
$$D[i][j] = \begin{cases} 0 \quad \text{if } i=0, j=0,\\ i \quad \text{if } j=0,\\ j \quad \text{if } i=0,\\ \min \begin{cases} D[i-1][j]+1 \quad \text{(delete)},\\ D[i][j-1]+1 \quad \text{(insertion)},\\ D[i-1][j-1]+c \quad \text{(substitution, if A[i-1]=B[j-1] then c=0, otherwise c=1)} \end{cases} \end{cases}$$
Duyệt qua từng ký tự của \(A\) và \(B\) để tính \(D[i][j]\) dựa trên công thức bên trên
\(D[m][n]\) chính là khoảng cách Levenshtein của \(A\) và \(B\)