Tải bản đầy đủ (.docx) (8 trang)

Tìm hiểu thuật toán so khớp xâu kí tự

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (154.63 KB, 8 trang )

Tìm hiểu thuật toán so khớp xâu kí tự (string matching)
Nguyễn Thành Đạt, Ngô Văn Dũng – K59B – Khoa Công nghệ Thông tin – Đại học
Sư phạm Hà Nội
Hướng dẫn: TS. Phạm Đức Đông – Khoa Toán tin – Đại học Sư phạm Hà Nội
Tóm tắt: Đề tài bài tập lớn môn học này đã trình bày những tìm hiểu về cách thức
hoạt động của hai trong số nhiều thuật toán so khớp xâu kí tự (string searching
algorithm/ string matching algorithm) là thuật toán Knuth-Morris-Pratt (KMP) và
thuật toán Rabin-Karp. Bên cạnh đó là việc đánh giá độ phức tạp của hai thuật toán, so
sánh tính khả dụng của chúng với các thuật toán khác để giải những bài toán tương tự
với những trường hợp cụ thể, cũng như cài đặt chương trình mô phỏng các thuật toán
bằng ngôn ngữ lập trình C++.
1. Mở đầu
Trong ngành Khoa học máy tính, các thuật toán so khớp xâu kí tự (hay còn gọi là thuật
toán tìm kiếm chuỗi) là một trong những bài toán cơ bản và quan trọng nhất. Mục đích
của thuật toán là tìm kiếm vị trí của các chuỗi con (còn được gọi là các pattern) trong
một chuỗi kí tự hoặc một văn bản lớn. Bài toán này có tính ứng dụng quan trọng trên
thực tế. Chẳng hạn như bài toán Tìm các pattern trong các chuỗi-DNA là bài toán cơ
bản đối với ngành Tin sinh học. Các phần mềm diệt virus hiện đại có chứa hàng chục
triệu các pattern là các “dấu hiệu” (virus signature) của các con virus mà máy tính đã
biết. Khi quét virus thì phần mềm diệt virus phải tìm kiếm các pattern này trong các
files hay bộ nhớ của máy…[1]
2. Thuật toán Knuth-Morris-Pratt
2.1.
Sơ lược
Nếu chỉ tính các thuật toán tìm kiếm sự xuất hiện của một chuỗi đơn cho trước bên
trong một chuỗi khác thì đã có đến hàng trăm thuật toán khác nhau, đã được thống kê
ở tài liệu tham khảo [2]. Thuật toán Knuth-Morris-Pratt (KMP) là một trong số hàng
trăm thuật toán đó.
Thuật toán KMP được tìm ra bởi hai nhà Khoa học máy tính, Giáo sư danh dự của Đại
học Stanford là Donald Ervin Knuth và Vaughan Ronald Pratt cùng với Giáo sư
người Mĩ James Hiram Morris vào năm 1974, họ nghiên cứu độc lập với nhau, tuy


nhiên, sau đó cả ba người đã cùng công bố thuật toán này vào năm 1977. [3]
Ý tưởng của thuật toán KMP đơn giản là tìm kiếm sự xuất hiện của một “từ” W trong
một “xâu văn bản” S bằng cách tiếp tục quá trình tìm kiếm khi không phù hợp, chính
từ cho ta đầy đủ thông tin để xác định vị trí bắt đầu của kí tự so sánh tiếp theo, do đó
bỏ qua quá trình kiểm tra lại các kí tự đã so sánh trước đó.
2.2.
Hoạt động của thuật toán


Bài toán
Input: Xâu văn bản S và một chuỗi W
Output: Vị trí của chuỗi W trong xâu văn bản S
Ý tưởng
Giả sử cho xâu văn bản S: CCABCABCABCABCD.
Tìm chuỗi W: ABCABCD trong xâu văn bản S.
Ta đưa thêm vào hai biến m và i. Trong đó: m là vị trí tương ứng trên xâu S bắt đầu
cho một phép so sánh với W và i là chỉ số chỉ số trên w xác định kí tự đang được so
sánh.
m 0
S CCABCABCABCABCD
W ABCABCD
i 0
Bắt đầu so sánh các kí tự tương ứng của S và W.
S[0] =”C” ≠ W[0] =”A” → m++
m 1
S CCABCABCABCABCD
W _ABCABCD
i _0
S[1] =”C” ≠ W[0] =”A” → m++
m 2

S CCABCABCABCABCD
W __ABCABCD
i __0
S[2] = W[0] =”A” → i++
S[3] = W[1] =”B” → i++
S[4] = W[2] =”C” → i++
S[5] = W[3] =”A” → i++
S[6] = W[4] =”B” → i++
m 2
S CCABCABCABCABCD
W __ABCABCD
i __5
S[7] = W[5] =”C” → i++
m 2
S CCABCABCABCABCD


W
i

__ABCABCD
__6

S[8] =”A” ≠ W[6] =”D”
Nhận thấy không có kí tự “D” trong 8 kí tự đầu của xâu S , tăng m lên 8 để bắt đầu
xét từ đây, trả lại i=0 để xét lại từ kí tự đầu của xâu W.
m ___________8
S CCABCABCABCABCD
W ___________ABCABCD
I ___________0

S[8] = S[0] = “A” → i++
S[9] = S[1] = “B” → i++
S[10] = S[2] = “C” → i++
S[11] = S[3] = “A” → i++
S[12] = S[4] = “B” → i++
S[13] = S[5] = “C” → i++
S[14] = S[6] = “D” → i++
Vậy đã tìm ra vị trí của chuỗi W trong xâu văn bản S ở vị trí m=8.
Thuật toán
Input: Xâu văn bản (S) và chuỗi con (W) cần tìm.
Output: vị trí tìm thấy W trong xâu S.
BEGIN
m = 0;
i = 0;
{MẢNG 1 CHIỀU T}; // Sử dụng hàm xây dựng bảng KMP
WHILE ((m+i)BEGIN
IF (W[i] = S[m+i]) THEN i=i+1;
IF (i=length(W)) THEN return(m) ELSE (m=m+i+T[i]);
IF (T[i] > -1 ) THEN i = -1 ELSE i = 0;
END;
Writeln(“Không tìm ra chuỗi W trong xâu S”);
END.
Trong thuật toán trên, bước đầu tiên cần xây dựng mảng một chiều T. Mảng T[i] sinh
ra để chỉ khoảng cách từ kí tự thứ i tới kí tự giống kí tự đầu tiên (với điều kiện kí tự
thứ i phải giống kí tự đó). Mảng một chiều T được gọi là bảng KMP.
Xây dựng bảng KMP
Input: chuỗi con W cần tìm, mảng T
Output: bảng KMP



BEGIN
Var pos, cnd: integer;
pos = 2;
cnd = 0;
T[0] = -1;
WHILE (pos < length(W)) DO
BEGIN
IF (W[pos-1] = W[cnd]) THEN
BEGIN
T[pos] = cnd + 1;
pos = pos +1;
cnd = cnd +1;
END;
ELSE IF (cnd > 0) THEN cnd = T[cnd] ELSE
BEGIN
T[pos] = 0;
pos = pos +1;
END;
END;
END.

Bảng KMP cho ví dụ trên như sau:
i
0
1
2
3
4
5

W[i]
A B C A B C
T[i]
-1 0
0
0
1
2

6
D
3

T[0] = -1;
Để tính T[1] cần tìm một xâu con “A” đồng thời là xâu con bắt đầu của W → T[1] = 0;
Tương tự, T[2] = 0;
Tới W[3], ta thấy kí tự này trùng với kí tự bắt đầu của xâu W[0]. Nhưng T[i] là độ dài
xâu dài nhất trùng với xâu con bắt đầu trong W tính đến W[i-1] nên T[3] = 0 và T[4] =
1;
Tương tự, kí tự W[4] trùng với kí tự W[1] nên T[5] = 2, kí tự W[5] trùng với kí tự
W[2] nên T[6] = 3.
2.3.
Đánh giá độ phức tạp của thuật toán
Thuật toán xây dựng bảng KMP có độ phức tạp O(n) với 1 vòng lặp while.
Thuật toán chính có độ phức tạp O(k) với 1 vòng lặp while.
Với trường hợp tốt nhất, chuỗi kí tự không bị lặp, độ phức tạp là O(n+k).


Với trường hợp xấu nhất, độ phức tạp là O(n.k), khi chuỗi có các kí tự lặp lại nhiều
lần.

3. Thuật toán Rabin-Karp
3.1.
Sơ lược
Thuật toán Rabin-Karp được phát minh bởi nhà khoa học máy tính người Israel
Michael O.Rabin và nhà khoa học máy tính, nhà toán học lý thuyết người Mĩ
Richard M.Karp vào năm 1987 [4]. Thuật toán Rabin-Karp sử dụng hàm băm (hash)
để tìm kiếm một chuỗi con (pattern) trong một xâu văn bản. Thuật toán sử dụng hàm
băm để so sánh các giá trị băm của các chuỗi trước khi thực sự so sánh chuỗi. Phương
pháp này giúp tiết kiệm thời gian so sánh, đặc biệt là với các chuỗi so sánh dài.
3.2.
Hàm băm
3.2.1. Hàm băm cơ bản
Hàm băm là giải thuật nhằm sinh ra các giá trị băm tương ứng với mỗi khối dữ liệu,
một chuỗi kí tự, một đối tượng trong lập trình hướng đối tượng,.. Giá trị băm đóng vai
gần như một khóa để phân biệt các khối dữ liệu, tuy nhiên, người ta chấp hiện tượng
trùng khóa hay còn gọi là đụng độ và cố gắng cải thiện giải thuật để giảm thiểu sự
đụng độ đó. Hàm băm thường được dùng trong bảng băm nhằm giảm chi phí tính toán
khi tìm một khối dữ liệu trong một tập hợp, nhờ việc so sánh các giá trị băm nhanh
hơn việc so sánh những khối dữ liệu có kích thước lớn. [5]
Một hàm băm đơn giản nhất đó là tính toán giá trị băm dựa trên mã ASCII hoặc
UNICODE của từng ký tự.
Ví dụ với chuỗi nguồn “abcdefgh” và chuỗi cần tìm có độ dài 4 thì giá trị băm đầu
tiên sẽ là:
h1 = a + b + c + d
= 97 + 98 + 99 + 100
= 394
Giá trị băm tiếp theo cần tính là
h2 = b + c + d + e
= h1 – a + e
= 394 – 97 + 101

= 398
Các hàm băm kiểu này có tốc độ tính toán cao, giá trị băm chỉ được tính toán lần đầu,
các giá trị tiếp theo được tính từ giá trị trước đó. Phép tính băm này không phụ thuộc
vào số lượng kí tự cần tìm kiếm. Tuy nhiên hàm băm này lại có khả năng gây trùng
cao do phép tính cộng giá trị quá đơn giản. Chẳng hạn với chuỗi cần tìm “bcde” thì
các chuỗi “cbed”, “ccdd”, “acak”…sẽ có cùng giá trị băm.
Vì vậy, một hàm băm tốt cần thỏa mãn các điều kiện sau:
- Tính toán nhanh.


- Các khóa được phân bố đều trong bảng.
- Ít xảy ra đụng độ.
- Xử lý được các loại khóa có kiểu dữ liệu
3.2.2. Hàm băm Rolling hash

khác nhau.

Để giảm thiểu việc trùng giá trị băm mà vẫn đảm bảo tính toán các giá trị băm nhanh
dựa trên giá trị băm trước đó, hàm băm cho thuật toán Rabin-Karp sử dụng ý tưởng cơ
số. Phép tính băm này gọi là Rolling hash.
Ý tưởng là chúng ta sẽ khai thác một vùng nhớ lớn bằng cách xem mỗi đoạn M-ký tự
có thể có của văn bản như là một khoá (key) trong một bảng băm chuẩn. Nhưng không
cần thiết phải giữ một bảng băm tổng thể, vì bài toán được cài đặt sao cho chỉ một
khoá là đang được tìm kiếm; việc mà ta cần làm là đi tính hàm băm cho M ký tự từ
văn bản vì nó chỉ đơn giản là kiểm tra xem chúng có bằng với mẫu hay không. Với
hàm băm: h(k) = k mod q, ở đây q (kích thước bảng) là một số nguyên tố lớn. Trong
trường hợp này, không có gì được chứa trong bảng băm, vì vậy q có thể được cho giá
trị rất lớn.
Phương pháp này dựa trên việc tính hàm băm cho vị trí i trong văn bản, cho trước giá
trị tại ví trí i-1 của nó, và suy ra hoàn toàn trực tiếp từ công thức toán học. Giả sử rằng

ta dịch M ký tự thành số bằng cách nén chúng lại với nhau trong một từ (word) của
máy, mà ta xem như một số nguyên. Điều này ứng với việc ghi các ký tự như các con
số trong một hệ thống cơ số d, ở đây d là số ký tự có thể có. Vì vậy số ứng với
a[i..i+M-1] là
x = a[i]dM-1 + a[i+1]dM-2 + …+ a[i+M-1]d0
Và có thể giả sử rằng ta biết giá trị của h(x) = x mod q. Nhưng dịch một vị trí sang
phải trong văn bản tương ứng với việc thay x bởi (x - a[i]dM-1)d + a[i+M].
Một tính chất cơ bản của phép toán mod là ta có thể thực hiện nó bất kỳ lúc nào trong
các phép toán này và vẫn nhận được cùng câu trả lời. Cách khác, nếu ta lấy phần dư
khi chia cho q sau mỗi một phép toán số học (để giữ cho các số mà ta đang gặp là
nhỏ), thì ta sẽ nhận được cùng câu trả lời như thể ta đã thực hiện tất cả các phép toán
học, sau đó lầy phần dư khi chia cho q.
3.3.
Phép đối sánh mẫu Rabin-Karp
FUNCTION RABINKARP: integer;
CONST q=33253586; d = 32;
VAR h1, h2, dM, i: integer;
BEGIN
dM:= 1;
FOR i:=1 TO M-1 DO dM:= (d*dM) MOD q;
h1: = 0;
FOR i: = 1 TO M DO h1:= (h1*d+index(p[i])) MOD q;
h2: = 0;


FOR i:= 1 TO M DO h2:= (h2*d + index (a[i])) MOD q;
i: = 1;
WHILE (h1 <> h2) AND(i<=N-M) DO
BEGIN
h2:= (h2+d*q-index(a[i])*dM) MOD q;

h2:= (h2*d+index(a[i+M])) MOD q;
i:= i+1;
END;
RABINKARP:= i;
END;
Chương trình giả định dùng hàm index (function index(c: char): integer; hàm trả về 0
đối với các khoảng trắng và i đối với ký tự thứ i của bảng chữ cái) nhưng d = 32 để
cho hiệu quả (các phép nhân có thể được cài đặt như các phép dịch bit).
Đầu tiên chương trình tính giá trị h1 cho mẫu, sau đó tới giá trị h2 cho M ký tự đầu
tiêncảu văn bản (nó cũng tính giá trị của dM-1mod q trong biến dM). Sau đó nó tiến
hành công việc qua chuỗi văn bản, dùng đến kỹ thuật ở trên để tính hàm băm cho M
ký tự với h1. Số nguyên tố q được chọn càng lớn càng tốt, nhưng đủ nhỏ sao cho
(d+1)*q không gây ra tràn: điều này cần ít phép mod hơn nếu ta dùng số nguyên tố lớn
nhất biểu diễn được (một giá trị d*q phụ trợ được cộng thêm vào trong khi tính h2 để
bảo đảm rằng mọi đại lượng vẫn còn là dương để cho phép toán mod có thể thực hiện
được).
Phép đối sánh mẫu Rabin-Karp gần như là tuyến tính.
Thuật toán này hiển nhiên thực hiện theo thời gian tỉ lệ với M+N, nhưng chú ý là nó
chỉ thực sự đi tìm một vị trí trong văn bản có cùgn giá trị băm với mẫu. Để cho chắc
chắn, ta nên thực sự tiến hành so sánh trực tiếp văn bản đó với mẫu. Tuy nhiên, việc sử
dụng giá trị rất lớn của q, được biến thành dương bởi các phép toán mod và bởi sự
kiện làta không cần duy trì bảng băm thực sự, đã khiến cho rất khó xảy ra một sự đụng
độ. Về mặt lý thuyết, thuật toán này có thể vẫn thực hiện theo O(NM) bước trong
trường hợp xấu nhất ( không đáng tin cậy), nhưng trong thực tế có thể dựa vào thuật
toán để thực hiện khoảng N+M bước.[5]
3.4.
Đánh giá độ phức tạp
Đối với xâu văn bản độ dài n và với p pattern chiều dài m, trường hợp trung bình và
trường hợp tốt nhất có độ phức tạp là O(m+n) trong không gian O(p). Trường hợp tồi
nhất có độ phức tạp là O(mn). [4]

Việc chuẩn bị trong thuật toán Karp-Rabin có độ phức tạp O(m). Tuy vậy thời gian tìm
kiếm lại tỉ lệ với O(m*n) vì có thể có nhiều trường hợp hàm băm của chúng ta bị lừa
và không phát huy tác dụng. Nhưng đó chỉ là những trường hợp đặc biệt, thời gian tính
toán của thuật toán KR trong thực tế thường tỉ lệ với O(n+m). Hơn nữa thuật toán
Rabin-Karp có thể dễ dàng mở rộng cho các mẫu, văn bản dạng 2 chiều, do đó khiến
cho nó trở nên hữu ích hơn so với các thuật toán còn lại trong việc xử lý ảnh.[5]
4. Kết luận


Trong đề bài Bài Tập Lớn Môn Học lần này, chúng em đã tìm hiểu cách thức hoạt
động cũng như cài đặt hai trong số rất nhiều thuật toán tìm kiếm xâu kí tự (string
matching) là thuật toán Knuth-Morris-Pratt và thuật toán Rabin-Karp. Dù đã dành
nhiều thời gian tìm hiểu và hoàn thiện bài tập nhưng chắc chắn sẽ không tránh khỏi sai
sót. Chúng em mong sẽ nhận được những lời nhận xét và đóng góp ý kiến từ thầy cô
cũng như các bạn để bài tập được hoàn thiện hơn. Xin chân thành cảm ơn.

TÀI LIỆU THAM KHẢO
[1] T.H.Cormen, C.E.Leiserson, R.L.Rivest, C.Stein; Introduction to Algorithms,
Second Edition.
[2] Simone Faro, Thierry Lecroq; The Exact String Matching Problem: a
Comprehensive Experimental Evaluation
[3]
/>%E2%80%93Pratt_algorithm
[4] />[5] Nguyễn Văn Quyết, Bài toán tìm kiếm văn bản sử dụng giải thuật di truyền , Luận
văn Thạc sĩ Công nghệ thông tin, Đại học Thái Nguyên, 2009.



×