Levenshtein Distance (Edit Distance) đo lường độ khác biệt giữa 2 chuỗi: số phép toán tối thiểu (chèn, xóa, thay thế) để biến chuỗi này thành chuỗi kia.
Ví dụ
"cat"→"mat": 1 (thay 'c' → 'm')"kitten"→"sitting": 3 (k→s, e→i, chèn g)"tutorials"→"point": 8
Quy hoạch động (Dynamic Programming)
Bảng dp[i][j] = khoảng cách giữa s1[0..i-1] và s2[0..j-1].
- Cơ sở:
dp[0][j] = j(chèn j ký tự),dp[i][0] = i(xóa i ký tự). - Chuyển trạng thái:
if (s1[i-1] == s2[j-1]) cost = 0; else cost = 1; dp[i][j] = min( dp[i-1][j] + 1, // Xóa dp[i][j-1] + 1, // Chèn dp[i-1][j-1] + cost // Thay thế / giữ );
Chương trình C++ tối ưu không gian
#include <bits/stdc++.h>
using namespace std;
// O(n*m) time, O(min(n,m)) space
int levenshtein(const string& s1, const string& s2) {
int m = s1.size(), n = s2.size();
if (m < n) return levenshtein(s2, s1); // Đảm bảo s1 dài hơn
vector prev(n + 1), curr(n + 1);
iota(prev.begin(), prev.end(), 0); // 0, 1, 2, ..., n
for (int i = 1; i <= m; ++i) {
curr[0] = i;
for (int j = 1; j <= n; ++j) {
int cost = (s1[i-1] == s2[j-1]) ? 0 : 1;
curr[j] = min({prev[j] + 1, // Xóa
curr[j-1] + 1, // Chèn
prev[j-1] + cost}); // Thay thế
}
swap(prev, curr);
}
return prev[n];
}
// Phiên bản trả về cả thao tác (traceback)
vector getOperations(const string& s1, const string& s2) {
int m = s1.size(), n = s2.size();
vector> dp(m+1, vector(n+1));
for (int i = 0; i <= m; ++i) dp[i][0] = i;
for (int j = 0; j <= n; ++j) dp[0][j] = j;
for (int i = 1; i <= m; ++i)
for (int j = 1; j <= n; ++j) {
int cost = (s1[i-1] == s2[j-1]) ? 0 : 1;
dp[i][j] = min({dp[i-1][j]+1, dp[i][j-1]+1, dp[i-1][j-1]+cost});
}
// Truy vết
vector ops;
int i = m, j = n;
while (i > 0 || j > 0) {
if (i > 0 && j > 0 && dp[i][j] == dp[i-1][j-1] + (s1[i-1]==s2[j-1]?0:1)) {
if (s1[i-1] != s2[j-1]) ops.push_back("Thay '" + string(1,s1[i-1]) + "' -> '" + string(1,s2[j-1]) + "'");
--i; --j;
} else if (i > 0 && dp[i][j] == dp[i-1][j] + 1) {
ops.push_back("Xoa '" + string(1,s1[i-1]) + "'");
--i;
} else {
ops.push_back("Chen '" + string(1,s2[j-1]) + "'");
--j;
}
}
reverse(ops.begin(), ops.end());
return ops;
}
int main() {
string a = "tutorials", b = "point";
cout << "Levenshtein(\"" << a << "\", \"" << b << "\") = "
<< levenshtein(a, b) << '\n';
cout << "\nCac buoc bien doi:\n";
for (const auto& op : getOperations(a, b))
cout << " - " << op << '\n';
// Test thêm
cout << "\nkitten -> sitting: " << levenshtein("kitten", "sitting") << '\n'; // 3
cout << "saturday -> sunday: " << levenshtein("saturday", "sunday") << '\n'; // 3
return 0;
}
Kết quả
Levenshtein("tutorials", "point") = 8
Cac buoc bien doi:
- Xoa 't'
- Xoa 'u'
- Xoa 't'
- Xoa 'o'
- Xoa 'r'
- Xoa 'i'
- Xoa 'a'
- Xoa 'l'
- Xoa 's'
- Chen 'p'
- Chen 'o'
- Chen 'i'
- Chen 'n'
- Chen 't'
(Chú ý: traceback đơn giản có thể không tối ưu về số bước in ra, nhưng khoảng cách = 8 là đúng)
kitten -> sitting: 3
saturday -> sunday: 3
Ứng dụng thực tế
- Spell checker / Autocorrect (tìm từ gần nhất trong từ điển).
- DNA sequence alignment (bioinformatics).
- Plagiarism detection, diff tools (git diff).
- Speech recognition (đo sai số nhận dạng).
- Fuzzy string matching (tìm kiếm gần đúng).
Biến thể
| Biến thể | Chi phí phép toán | Ứng dụng |
|---|---|---|
| Levenshtein chuẩn | Insert=1, Delete=1, Replace=1 | Chung |
| Damerau-Levenshtein | + Transpose (hoán vị 2 ký tự liền kề)=1 | Typo thường gặp (teh→the) |
| Weighted Edit Distance | Trọng số khác nhau cho từng phép | OCR, DNA (transition/transversion) |
| Longest Common Subsequence (LCS) | Chỉ cho phép Insert/Delete | Diff, version control |
Levenshtein Distance là thuật toán kinh điển, nền tảng của nhiều ứng dụng xử lý ngôn ngữ tự nhiên và so sánh chuỗi.