Mục đích
Tìm chính xác (exact match) chuỗi con (substring) trong một chuỗi dài hơn.
Ý tưởng chính
Gọi chuỗi cần tìm là P (pattern), và chuỗi dài là T (text).
So sánh ngược P trong T, nghĩa là ta sẽ so sánh ký tự cuối của P trước, sau đó so sánh ký tự kế cuối, và lần lượt như vậy đến ký tự đầu tiên trong P. Gọi vị trí trong T để bắt đầu so sánh hai chuỗi là i. Việc so sánh sẽ so sánh lần lượt T[i] với ký tự cuối của P, rồi T[i-1] với ký tự kế cuối của P, v.v...
Nếu việc so sánh ngược vượt qua được ký tự đầu tiên trong P, ta đã tìm được P trong T.
Nếu việc so sánh ngược bị thất bại, ta sẽ căn P cho khớp với T[i] và thử lại việc so sánh ngược. Điều này tương đương với việc dịch chuyển i đến vị trí xa hơn trong T. Đây là ý tưởng chủ chốt của thuật toán BMH.
- Nếu T[i] không có trong P, thì ta có thể dịch chuyển i đến vị trí i + len(P).
- Nếu vị trí cuối cùng của T[i] trong P là x thì ta dịch chuyển i đến vị trí i + len(P) - x - 1.
Trạng thái bắt đầu
i
|
v
+-+-+-+-+-+-+-+-+-+-+-+-+
| | | | |d|b|a| | | | | |
+-+-+-+-+-+-+-+-+-+-+-+-+
+-+-+-+-+-+
|a|a|c|b|a|
+-+-+-+-+-+
------------------------------------------
So sánh tiếp tục
i
|
v
+-+-+-+-+-+-+-+-+-+-+-+-+
| | | | |d|b|a| | | | | |
+-+-+-+-+-+-+-+-+-+-+-+-+
=
+-+-+-+-+-+
|a|a|c|b|a|
+-+-+-+-+-+
------------------------------------------
So sánh tiếp tục
i
|
v
+-+-+-+-+-+-+-+-+-+-+-+-+
| | | | |d|b|a| | | | | |
+-+-+-+-+-+-+-+-+-+-+-+-+
= =
+-+-+-+-+-+
|a|a|c|b|a|
+-+-+-+-+-+
------------------------------------------
So sánh sai
i
|
v
+-+-+-+-+-+-+-+-+-+-+-+-+
| | | | |d|b|a| | | | | |
+-+-+-+-+-+-+-+-+-+-+-+-+
! = =
+-+-+-+-+-+
|a|a|c|b|a|
+-+-+-+-+-+
------------------------------------------
Căn P theo T[i]...
i
|
v
+-+-+-+-+-+-+-+-+-+-+-+-+
| | | | |d|b|a| | | | | |
+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+
|a|a|c|b|a|
+-+-+-+-+-+
------------------------------------------
... cũng có nghĩa là dịch chuyển i
i
|
v
+-+-+-+-+-+-+-+-+-+-+-+-+
| | | | |d|b|a| | | | | |
+-+-+-+-+-+-+-+-+-+-+-+-+
+-+-+-+-+-+
|a|a|c|b|a|
+-+-+-+-+-+
Để cài đặt hai bước trên, người ta thường dùng một mảng chứa vị trí cuối cùng của các ký tự trong P trừ ký tự cuối cùng (P[ : -1]).
Ví dụ
Giả sử chúng ta muốn tìm chuỗi needle trong chuỗi find the needle in the haystack.
Trước khi bắt đầu, ta sẽ lập bảng vị trí cuối của các ký tự trong P[ : -1].
Ký tự | Vị trí | Diễn giải |
---|---|---|
n | 0 | |
e | 2 | Ta chọn vị trí của ký tự thứ hai. |
d | 3 | |
l | 4 |
Sau đó ta sẽ xem xét sự thay đổi ở các bước của thuật toán. Vì độ dài của P là 6, nên i sẽ bắt đầu từ vị trí 5. Trong bảng dưới, chữ đậm là ký tự trùng nhau của T và P, chữ gạch dưới là các ký tự đang xét.
i | T | T[i] | Diễn giải |
---|---|---|---|
5 | find the needle in the haystack | t | t không có trong bảng. Dịch i lên 11 (5 + 6). |
11 | find the needle in the haystack | e | e có trong bảng. Dịch i lên 14 (11 + 6 - 2 - 1). |
14 | find the needle in the haystack | e | Tìm thấy. e có trong bảng. Dịch i lên 17 (14 + 6 - 2 - 1). |
17 | find the needle in the haystack | n | n có trong bảng. Dịch i lên 22 (17 + 6 - 0 - 1). |
22 | find the needle in the haystack | khoảng trắng | khoảng trắng không có trong bảng. Dịch i lên 28 (22 + 6). |
28 | find the needle in the haystack | a | a không có trong bảng. Dịch i lên 34 (28 + 6). Dừng việc tìm kiếm vì đã xét hết chuỗi. |
Độ phức tạp
- Thời gian chạy
- Tệ nhất là O(n*m), với n là độ dài của T và m là độ dài của P. Trung bình là O(n). Và tốt nhất là dưới tuyến tính (sublinear) vì thuật toán có thể nhảy qua nhiều ký tự. Trong ví dụ trên, ta chỉ cần 12 phép so sánh để tìm chuỗi needle (6 ký tự) trong chuỗi find the needle in the haystack (31 ký tự).
- Bộ nhớ cần thiết
- O(n) với n là số ký tự trong bảng chữ cái (ví dụ như 256 giá trị của một byte, hoặc nếu bảng chữ cái là Unicode thì có thể sẽ nhiều hơn 60000 giá trị) vì chúng ta cần tạo bảng vị trí các ký tự trong P.
Thuật toán BMH là thuật toán tìm chuỗi chính xác tổng quát nhất, nhanh nhất, và đơn giản nhất.