Thuật toán đƣợc phát minh năm 1977 bởi hai giáo sƣ của đại học Stanford, Hoa Kỳ (một trong số ít các trƣờng đại học xếp hàng số một về khoa học máy tính trên thế giới cùng với MIT, CMU cũng của Hoa Kỳ và Cambridge của Anh) Donal Knuth và Vaughan Ronald Pratt, Knuth (giải Turing năm 1971) . Thuật toán này còn có tên là KMP lấy tên viết tắt của ba ngƣời phát minh ra nó, chữ “M” là chỉ giáo sƣ J.H.Morris, một ngƣời cũng rất nổi tiếng trong khoa học máy tính.
Ý tƣởng chính của phƣơng pháp này nhƣ sau: trong quá trình tìm kiếm vị trí của mẫu P trong xâu gốc T, nếu tìm thấy một vị trí sai ta chuyển sang vị trí tìm kiếm tiếp theo và quá trình tìm kiếm sau này sẽ đƣợc tận dụng thông tin từ quá trình tìm kiếm trƣớc để không phải xét các trƣờng hợp không cần thiết [8].
Ví dụ: tìm mẫu w = “ABCDABD” trong xâu T = “ABC ABCDAB ABCDABCDABDE”. Ở mỗi thời điểm, thuật toán luôn đƣợc xác định bằng hai biến kiểu nguyên, m và i, đƣợc định nghĩa lần lƣợt là vị trí tƣơng ứng trên S bắt đầu cho một phép so sánh với W, và chỉ số trên W xác định kí tự đang đƣợc so sánh. Khi bắt đầu, thuật toán đƣợc xác định nhƣ sau:
m: 0
S: ABC ABCDAB ABCDABCDABDE W: ABCDABD
i: 0
Chúng ta tiến hành so sánh các kí tự của W tƣơng ứng với các kí tự của S, di chuyển lần lƣợt sang các chữ cái tiếp theo nếu chúng giống nhau. S[0] và W[0] đều là „A‟. Ta tăng i:
Số hóa bởi Trung tâm Học liệu http://www.lrc-tnu.edu.vn/ m: 0
S: ABC ABCDAB ABCDABCDABDE W: ABCDABD
i: _1
S[1] và W[1] đều là „B‟. Ta tiếp tục tăng i:
m: 0
S: ABC ABCDAB ABCDABCDABDE W: ABCDABD
i: __2
S[2] và W[2] đều là „C‟. Ta tăng i lên 3:
m: 0
S: ABC ABCDAB ABCDABCDABDE W: ABCDABD
i: ___3
Nhƣng, trong bƣớc thứ tƣ, ta thấy S[3] là một khoảng trống trong khi W[3] = 'D', không phù hợp. Thay vì tiếp tục so sánh lại ở vị trí S[1], ta nhận thấy rằng không có kí tự 'A' xuất hiện trong khoảng từ vị trí 0 đến vị trí 3 trên xâu S ngoài trừ vị trí 0; do đó, nhờ vào quá trình so sánh các kí tự trƣớc đó, chúng ta thấy rằng không có khả năng tìm thấy xâu dù có so sánh lại. Vì vậy, chúng ta di chuyển đến kí tự tiếp theo, gán m = 4 và i = 0.
m: ____4
S: ABC ABCDAB ABCDABCDABDE W: ABCDABD
i: 0
Tiếp tục quá trình so sánh nhƣ trên, ta xác định đƣợc xâu chung "ABCDAB", với W[6] (S[10]), ta lại thấy không phù hợp. Nhƣng từ kết quả của quá trình so sánh trƣớc, ta đã duyệt qua "AB", có khả năng sẽ là khởi đầu cho
Số hóa bởi Trung tâm Học liệu http://www.lrc-tnu.edu.vn/
một đoạn xâu khớp, vì vậy ta bắt đầu so sánh từ vị trí này. Nhƣ chúng ta đã thấy các kí tự này đã trùng khớp với nhau kí tự trong phép so khớp trƣớc, chúng ta không cần kiểm tra lại chúng một lần nữa; ta bắt đầu với m = 8, i = 2 và tiếp tục quá trình so khớp.
m: ________8
S: ABC ABCDAB ABCDABCDABDE W: ABCDABD
i: __2
Quá trình so khớp ngay lập tức thất bại, nhƣng trong W không xuất hiện kí tự „ „,vì vậy, ta tăng m lên 11, và gán i = 0.
m: ___________11
S: ABC ABCDAB ABCDABCDABDE W: ABCDABD
i: 0
Một lần nữa, hai xâu trùng khớp đoạn kí tự "ABCDAB" nhƣng ở kí tự tiếp theo, 'C', không trùng với 'D' trong W. Giống nhƣ trƣớc, ta gán m = 15, và gán i = 2, và tiếp tục so sánh.
m: _______________15
S: ABC ABCDAB ABCDABCDABDE W: ABCDABD i: __2
Lần này, chúng ta đã tìm đƣợc khớp tƣơng ứngvới vị trí bắt đầu là S[15]. Bảng so khớp một phần T giúp ta xác định đƣợc vị trí tiếp theo để so khớp khi phép so khớp trƣớc đã thất bại. Mảng T đƣợc tổ chức để nếu chúng ta có một phép so khớp bắt đầu từ S[m] thất bại khi so sánh S[m + i] với W[i], thì vị trí của phép so khớp tiếp theo có chỉ số là m + i - T[i] trong S (T[i] là đại lƣợng xác định số ô cần lùi khi có một phép so khớp thất bại). Mặc dù phép so khớp tiếp theo sẽ bắt đầu ở chỉ số m + i - T[i], giống nhƣ ví dụ ở trên, chúng ta không cần
Số hóa bởi Trung tâm Học liệu http://www.lrc-tnu.edu.vn/
so sánh các kí tự T[i] sau nó, vì vậy chúng ta chỉ cần tiếp tục so sánh từ kí tự W[T[i]]. Ta có T[0] = -1, cho thấy rằng nếu W[0] không khớp, ta không phải lùi lại mà tiếp tục phép so sánh mới ở kí tự tiếp theo. Sau đây là đoạn mã giả mẫu của thuật toán tìm kiếm KMP.
algorithm kmp_search: input:
mảng kí tự, S (đoạn văn bản) mảng kí tự, W (xâu đang tìm) output:
một biến kiểu nguyên (vị trí (bắt đầu từ 0) trên S mà W được tìm thấy) define variables:
biến nguyên, m ← 0 biến nguyên, i ← 0 mảng nguyên, T
while m + i nhỏ hơn độ dài của sâu S, do: if W[i] = S[m + i], let i ← i + 1 if i bằng độ dài W, return m otherwise, if T[i] > -1,
let i ← T[i], m ← m + i - T[i] else
let i ← 0, m ← m + 1 return độ dài của đoạn văn bản S
Với sự xuất hiện của mảng T, phần tìm kiếm của thuật toán Knuth– Morris–Pratt có độ phức tạp O(k), trong đó k là độ dài của xâu S. Ngoại trừ các thủ tục nhập xuất hàm ban đầu, tất cả các phép toán đều đƣợc thực hiên trong vòng lặp while, chúng ta sẽ tính số câu lệnh đƣợc thực hiện trong vòng lặp; để làm đƣợc việc này ta cần phải tìm hiểu về bản chất của mảng T. Theo định nghĩa, mảng đƣợc tạo để: nếu một phép so khớp bắt đầu ở vị trí S[m] thất bại khi
Số hóa bởi Trung tâm Học liệu http://www.lrc-tnu.edu.vn/
so sánh S[m + i] với W[i], thì phép so khớp có thể thành công tiếp theo sẽ bắt đầu ở vị trí S[m + (i - T[i])]. Cụ thể hơn, phép so khớp tiếp theo sẽ bắt đầu tại vị trí có chỉ số cao hơn m, vì vậy T[i] < i.
Từ điều này, ta thấy rằng vòng lặp có thế thức hiện 2k lần. Với mỗi lần lặp, nó thực hiện một trong hai nhánh của vòng lặp. Nhánh thứ nhất tăng i và không thay đổi m, vì vậy chỉ số m + i của kí tự đang so sánh trên S tăng lên. Nhánh thứ hai cộng thêm i - T[i] vào m, và nhƣ chúng ta đã biết, đây luôn là số dƣơng. Vì vậy, vị trí m, vị trí bắt đầu của một phép so khớp tiềm năng tăng lên. Vòng lặp dừng nếu m + i = k; vì vậy mỗi nhánh của vòng lặp có thể đƣợc sử dụng trong tối đa k lần, do chúng lần lƣợt tăng giá trị của m + i hoặc m, và m ≤ m + i: nếu m = k, thì m + i ≥ k, vì vậy: do các phép toán chủ yếu tăng theo đơn vị, chúng ta đã có m + i = k vào một thời điểm nào đó trƣớc, và vì vậy thuật toán dừng. Do đó vòng lặp chủ yếu thực hiện 2k lần, độ phức tạp tính toán của thuật toán tìm kiếm chỉ là O(k).