cac giai thuat ve xau ky tu

32
TIỂU LUẬN LÝ THUYẾT THUẬT TOÁN II Các giải thuật về xâu ký tự Nguyễn Tài Quyền-A06413 Ngày 9 tháng 6 năm 2006

Upload: cutithongtin

Post on 15-Jun-2015

1.302 views

Category:

Documents


7 download

DESCRIPTION

Cac giai thuat ve xau ki tu

TRANSCRIPT

Page 1: Cac Giai Thuat Ve Xau Ky Tu

TIỂU LUẬN

LÝ THUYẾT THUẬT TOÁN II

Các giải thuật về xâu ký tự

Nguyễn Tài Quyền-A06413

Ngày 9 tháng 6 năm 2006

Page 2: Cac Giai Thuat Ve Xau Ky Tu

MỤC LỤC

Trang

Chương 1 . Tìm kiếm chuỗi 11.1 Đôi nét về lịch sử . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.2 Thuật toán Brute-Force . . . . . . . . . . . . . . . . . . . . . . . . 3

1.2.1 Tư tưởng của thuật toán . . . . . . . . . . . . . . . . . . . 31.2.2 Phát biểu thuật toán và các ví dụ . . . . . . . . . . . . . . . 31.2.3 Những nhận xét về thuật toán . . . . . . . . . . . . . . . . 5

1.3 Thuật toán Knuth-Morris-Pratt . . . . . . . . . . . . . . . . . . . . 61.3.1 Tư tưởng của thuật toán . . . . . . . . . . . . . . . . . . . 61.3.2 Phát biểu thuật toán và các ví dụ . . . . . . . . . . . . . . . 61.3.3 Những nhận xét về thuật toán . . . . . . . . . . . . . . . . 10

1.4 Thuật toán Boyer-Moore . . . . . . . . . . . . . . . . . . . . . . . 111.4.1 Tư tưởng của thuật toán . . . . . . . . . . . . . . . . . . . 111.4.2 Phát biểu thuật toán và các ví dụ . . . . . . . . . . . . . . . 111.4.3 Những nhận xét về thuật toán . . . . . . . . . . . . . . . . 15

1.5 Thuật toán Rabin-Krap . . . . . . . . . . . . . . . . . . . . . . . . 151.5.1 Tư tưởng của thuật toán . . . . . . . . . . . . . . . . . . . 151.5.2 Phát biểu thuật toán và các ví dụ . . . . . . . . . . . . . . . 171.5.3 Những nhận xét về thuật toán . . . . . . . . . . . . . . . . 18

1.6 Đa truy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

Chương 2 . Đối sánh mẫu 202.1 Mô tả mẫu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212.2 Các máy đối sánh mẫu . . . . . . . . . . . . . . . . . . . . . . . . 222.3 Biểu diễn máy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252.4 Mô phỏng máy . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

i

Page 3: Cac Giai Thuat Ve Xau Ky Tu

MỤC LỤC ii

Tài liệu tham khảo 29

Page 4: Cac Giai Thuat Ve Xau Ky Tu

Chương 1

Tìm kiếm chuỗi

Chúng ta biết rằng chuỗi (string) là một dãy kí tự tuyến tính (có thể rất dài) đặc trưngcho một kiểu dữ liệu thường không được phân rã một cách logic thành các mẩu tinđộc lập với các thành phần nhỏ có thể phân biệt được với nhau.

Chuỗi là một thành phần trung tâm trong các hệ xử lý văn bản, là các hệ cung cấpnhiều khả năng để thao tác trên văn bản. Các hệ như vậy để xử lý các chuỗi văn bảnvà các chuỗi có thể định nghĩa một cách không chặt chẽ như là các dãy chữ, số vàcác ký tự đặc biệt. Những đối tượng như vậy có thể rất lớn và các thuật toán hiệuquả sẽ đóng vai trò quan trọng trong việc thao tác trên các chuỗi đó.

Kiểu chuỗi nhị phân là một dãy bao gồm các ký tự 0 và 1. Trong một ngữ cảnhnáo đó chuỗi nhị phân chỉ đơn giản là một kiểu chuỗi văn bản đặc biệt, tuy nhiênchúng ta cần phải phân biệt chuỗi nhị phân với chuỗi văn bản vì có những thuật toánkhác nhau sẽ thích hợp cho từng kiểu chuỗi; ngoài ra một điểm cần chú ý là mộtchuỗi văn bản luôn có thể biểu diễn qua một chuỗi nhị phân.

Cụ thể là ta có thể định nghĩa chuỗi văn bản là các đối tượng hoàn toàn khác biệtvới các chuỗi nhị phân vì húng được tạo ra từ các ký tự của một bảng chữ cái lớn.Mặt khác thì có thể coi hai kiểu chuối này là tương đương vì như ta đã biết một ký tựchữ cái có thể biểu diễn là một xâu 8 bits nhị phân; còn ngược lại một chuỗi nhị phâncó thể chuyển lại thành một chuỗi văn bản khi nhóm 8 bits lại với nhau để thành mộtký tự.

Một phép toán cơ bản trên chuỗi là "đối sánh mẫu" (Pattern Matching): Cho trướcmột chuỗi văn bản có độ dài N và một mẫu có độ dài M , hãy tìm sự xuất hiện củamẫu trong văn bản. Hầu hết các thuật toán cho bài toán này có thể dễ dàng mở rộngđể tìm tất cả các xuất hiện của mẫu trong văn bản, vì chúng sẽ quét qua toàn bộ vănbản một cách tuần tự và có thể bắt đầu ở điểm ngay sau một lần xuất hiện của mẫuđể tìm lần xuất hiện kế tiếp.

Bài toán "đối sánh mẫu" có thể đặc trưng như một bài toán tìm kiếm, trong đómẫu được xem như là khóa; tuy nhiên với các bài toán tìm kiếm mà ta đã nghiên cứukhông áp dụng được một cách trực tiếp vì mẫu có thể dài và do nó trải (lines up) trên

Page 5: Cac Giai Thuat Ve Xau Ky Tu

1.1. Đôi nét về lịch sử 2

văn bản theo một cách không biết trước được. Đây là một bài toán thú vị và nhiềuthuật toán khác nhau (cũng rất bất ngờ) chỉ mới được phát hiện gần đây không nhữngđã cung cấp một loạt các phương pháp thực tế hữu ích mà còn minh họa cho một vàikỹ thuật thiết kế thuật toán cơ sở.

1.1 Đôi nét về lịch sử

Để có thể đặt các phương pháp giải quyết các bài toán mà ta đã xem xét ở trên vàođúng từng bối cảnh lịch sử cụ thể chúng ta sẽ tìm hiểu đôi chút về lich sử của cácthuật toán.

Một thuật toán ở mức thô sơ mà hiện nay vẫn còn được sử dụng rộng rãi làBrute-Force. Mặc dù thời gian tồi nhất mà nó thực hiện tỉ lệ với tích M ×N , nhưngkhi ứng dụng vào thực tế các chuỗi phát sinh ra thường có xử lý tỉ lệ với tổngM +N .

Hơn thế nữa thuật toán này thích hợp với cấu trúc của hầu hết các hệ máy tính, vìvậy một chương trình ở mức tối ưu sẽ là một chuẩn mực và khó bị đánh bại bởi cácthuật toán thông minh hơn.

Trong năm 1970, S.A. Cook đã chứng minh một kết quả lý thuyết về một loạimáy trừu tượng mà nó suy ra một thuật toán để giải bài toán đối sánh mẫu, có thờigian tỉ lệ với M + N trong trường hợp xấu nhất. D.E. Knuth và V.R. Pratt đã theođuổi kiến trúc mà Cook đã dùng và đã tìm ra một thuật toán mà có thể tinh chế thànhmột thuật toán thực tiễn tương đối đơn giản. Điều đặc biệt là ở một cách tiếp cậnkhác J.H. Morris đã khám phá ra chính thuật toán đó khi mà ông gặp rắc rối trongkhi cài đặt một trình soạn thảo văn bản. Và chính điều này đã làm tăng sự tin tưởngvào thuật toán tìm được.

Knuth, Morris và Pratt đã không giới thiệu thuật toán của họ cho đến năm 1976và trong thời gian đó R.S. Boyer và J.S. Moore đã khám phá ra một thuật toán nhanhhơn nhiều trong nhiều ứng dụng vì nó thường chỉ kiểm tra một phần các ký tự trongchuỗi văn bản. Nhiều trình soạn thảo đã sử dụng thuật toán này để đạt được sự rútngắn đáng kể về mặt thời gian tìm kiếm chuỗi.

Cả hai thuật toán Knuth-Morris-Pratt và Boyer-Moore đều cần một chút xử lýphức tạp trên mẫu, khiến cho thuật toán trở nên khó hiểu từ đó làm hạn chế phạm visử dụng của chúng. Nhiều lập trình viên hệ thống đã thấy rằng thuật toán của Morrislà quá khó hiểu và thay vào đó họ sử dụng thuật toán Brute-Force.

Trong năm 1980, R.M. Karp và M.O. Rabin đã quan sát thấy rằng bài toán nàykhông khác lắm so với bài toán tìm kiếm chuẩn như người ta đã tưởng và đi đến mộtthuật toán đơn giản gần như thuật toán Brute-Force, có thời gian thực hiện luôn tỉ lệvới M + N . Hơn thế thuật toán của họ mở rộng dễ dàng cho các mẫu và văn bản haichiều, do đó nó trở nên hữu ích hơn cho các thuật toán còn lại trong việc xử lý ảnh.

Page 6: Cac Giai Thuat Ve Xau Ky Tu

1.2. Thuật toán Brute-Force 3

1.2 Thuật toán Brute-Force

1.2.1 Tư tưởng của thuật toán

Thuật toán Brute-Force để tìm sự xuất hiện của một chuỗi (được gọi là mẫu) trongmột văn bản bằng cách kiểm tra từng vị trí trong văn bản ở đó mẫu có thể khớp được,cho đến khi chúng khớp nhau thực sự.

1.2.2 Phát biểu thuật toán và các ví dụ

Một chương trình như sau cho phép tìm kiếm một mẫu p[1..M ] trong một văn bảna[1..N ]:

Algorithm 1 Thuật toán Brute-Forcefunction: brutesearch : integer;1: var i, j : integer;2: begin3: i := 1; j := 1;4: repeat5: if a[i] = p[j] then6: i := i + 1; j := j + 1;7: else8: i := i − j + 2; j := 1;9: end if10: until (j > M) or (i > N)11: if j > M then12: brutesearch := i − M ;13: else14: brutesearch := i;15: end if16: end;

Nếu ký tự thứ j trong mẫu khớp với ký tự thứ i trong văn bản thì ta tăng i và j lênmột đơn vị để so sánh hai ký tự tiếp theo. Trong trường hợp j > M (cuối mẫu) thìcó nghĩa là đã tìm thấy một sự trùng khớp của mẫu trong văn bản. Ngược lại nếu kýtự p[j] và a[i] không khớp nhau thì j := 1 để quay về đầu mẫu còn i := j − i + 2 đểdi chuyển mẫu sang phải so với văn bản một đơn vị và thực hiện so sánh lại. Khi tớicuối văn bản (i > N ) có nghĩa là không tìm thấy mẫu trong văn bản đã cho và hàmtrả về giá trị N + 1.

Ví dụ 1:Ta tiến hành tìm mẫu 10100111 trong một chuỗi văn bản nhị phân.Ta thử lấy xét một trường hợp ở dòng thứ dòng thứ 5: Đầu tiên ta có a[4] = p[1]

nên i := i + 1 => i = 5; j := j + 1 => j = 2. Sau đó xét thấy a[5] <> p[2] nên

Page 7: Cac Giai Thuat Ve Xau Ky Tu

1.2. Thuật toán Brute-Force 4

Hình 1.1: Ví dụ 1 - Thuật toán Brute-Force.

i := j − i + 2 => i = 5 − 2 + 2 = 5; j := 1. Chuyển sang dòng thứ 6 ta bắt đầu sosánh a[5] với p[1] tức là mẫu đã dịch sang phải 1 đơn vị so với xâu văn bản.

Ví dụ 2:

Hình 1.2: Ví dụ 2 - Thuật toán Brute-Force.

Page 8: Cac Giai Thuat Ve Xau Ky Tu

1.2. Thuật toán Brute-Force 5

Ví dụ 3:

Hình 1.3: Ví dụ 3 - Thuật toán Brute-Force.

1.2.3 Những nhận xét về thuật toán

Ta nhận thấy là việc tìm kiếm bằng Brute-Force có thể là rất chậm đối với một sốmẫu nào đó, ví dụ nếu xâu cần xét là một xâu nhị phân chỉ gồm hai ký tự thườngxuất hiện trong các ứng dụng xử lý ảnh và lập trình Hệ thống. Trở lại ví dụ 1 ta thấykhông có nhiều dòng mà có nhiều ký tự đầu tiên không khớp với đoạn xâu văn bảncần so sánh, ta gọi đó là "các khời đầu sai". Một điều hiển nhiên khi nghiên cứuthuật toán là làm sao hạn chế được càng nhiều "các khời đầu sai" như vậy càng tốt.

Tính chất 1.2.1Thuật toán Brute-Force có thể cần khoảng M × N phép so sánh ký tự.

Trường hợp xẫu nhất là khi cả mẫu lẫn văn bản tất cả đều là số 0 và kết thúc bởimột số 1. Khi đó với mỗi vị trí trong N −M + 1 vị trí đều có thể khớp với nhau, tấtcả các ký tự trên mẫu đều được so sánh với từng ký tự trên văn bản, do đó cần phảithực hiện (N − M + 1)M phép so sánh. Mặt khác thường thì M rất nhỏ so với N ,như vậy số phép so sánh ký tự xấp xỉ bằng M × N .

Các chuối như vậy có thể bắt gặp khi sử lý các văn bản nhị phân, vì vậy chúng tacần phải tìm các thuật toán tốt hơn.

Page 9: Cac Giai Thuat Ve Xau Ky Tu

1.3. Thuật toán Knuth-Morris-Pratt 6

1.3 Thuật toán Knuth-Morris-Pratt

1.3.1 Tư tưởng của thuật toán

Tư tưởng cơ bản của thuật toán Knuth-Morris-Pratt là thay vì ta so sánh mẫu với lầnlượt từng vị trí trong văn bản thì ta dựa vào các ký tự đã biết trước trong mẫu để làmsao giảm được số các phép so sánh. Cụ thể là khi phát hiện ra có sự không ăn chúngta không gán i như trong Brute-Force để dịch chuyển mẫu sang phải một bước đểtiếp tục so sánh với văn bản chúng ta tìm cách để dịch chuyển mấy với số đơn vị làlớn hơn hoặc bằng 1.

Điều đó có thể thực hiện được tùy thuộc vào mẫu mà ta cần tìm, ví dụ nếu mẫucó dạng là một xâu nhị phân có dạng đặc biệt là 10000000 (ký tự đầu tiên của mẫuchỉ xuất hiện duy nhất một lần). Khi đó giả sử có một khởi đầu sai dài k ký tự (tứclà k ký tự đầu tiên của mẫu là khớp với một đoạn nào đó trong văn bản). Như vậynếu ký tự thư j = k + 1 là không khớp thì ta đã biết rằng k ký tự trước trong mẫuđã khớp nhau rồi; điều đó có nghĩa là k ký tự này có dạng ở cả trong mẫu và vănbản là 100...0. Một điều hiển nhiên là ta không phải so sánh ký tự thứ nhất của mẫu(là 1) với k − 1 ký tự tiếp theo trong văn bản, hay nói cách khác dịch chuyển mẫusang phải k đơn vị (gán lại j = 1 và i giữ nguyên) để so sánh tiếp ký tự đầu tiên củamẫu với ký tự thứ i trong văn bản. Tuy nhiên trong thực tế hầu như không xảy ratrường hợp đặc biệt như vậy, và thuật toán Knuth-Morris-Pratt là sự tổng quá hóa từtư tưởng đó. Một điều rất đáng chú ý ở đây là ta luôn luôn có thể sắp xếp sao chocon trỏ i không bao giờ bị giảm đi cả.

Hình 1.4: Minh họa tư tưởng của thuật toán Knuth-Morris-Pratt.

1.3.2 Phát biểu thuật toán và các ví dụ

Trước hết chúng ta cần xây dựng một mảng next[1..M ] để xác định xem phải dựphòng một khoảng bao nhiêu khi phát hiện sự không ăn khớp. Chúng ta dịch chuyểnmột bản sao j − 1 ký tự đầu tiên của mẫu trên chính nó từ trái sang phải; bắt đầu từký tự đầu tiên của bản sao trên ký tự thứ hai của mẫu và ngừng lại khi các ký tự gối

Page 10: Cac Giai Thuat Ve Xau Ky Tu

1.3. Thuật toán Knuth-Morris-Pratt 7

khớp nhau hay không có ký tứ nào khớp nhau cả. Khoảng cách để dự phòng trongmẫu next[j] được xác định chính xác là cộng 1 với số các ký tự gối khớp nhau. Đặcbiệt là với một số j > 1 thì giá trị của next[j] là số k lớn nhất mà nhỏ hơn j sao chok − 1 ký tự đầu tiên của bản sao khớp với k − 1 ký tự cuối cùng trong j − 1 ký tựđầu tiên của mẫu. Mặt khác chúng ta cũng định nghĩa next[1] = 0. Ta có thuật toántính mảng next như sau:

Algorithm 2 Tính mảng nextprocedure: initnext;1: var i, j : integer;2: begin3: i := 1; j := 0; next[1] := 0;4: repeat5: if (j = 0) or (p[i] = p[j]) then6: i := i + 1; j := j + 1; next[i] := j;7: else8: j := next[j];9: end if10: until i > M11: end;

Ta thử chạy thuật toán trên với trường hợp sau:

Hình 1.5: Minh họa tính mảng next.

Page 11: Cac Giai Thuat Ve Xau Ky Tu

1.3. Thuật toán Knuth-Morris-Pratt 8

Và được kết quả như sau:

Result:1: i=1,j=0: i := 2; j := 1; next[2] := 1;2: i=2,j=1: j := next[1] = 0;3: i=2,j=0: i := 3; j := 1; next[3] := 1;4: i=3,j=1: i := 4; j := 2; next[4] := 2;5: i=4,j=2: i := 5; j := 3; next[5] := 3;6: i=5,j=3: j := next[3] = 1;7: i=5,j=1: j := next[1] = 0;8: i=5,j=0: i := 6; j := 1; next[6] := 1;9: i=6,j=1: i := 7; j := 2; next[7] := 2;10: i=7,j=2: j := next[2] = 1;11: i=7,j=1: i := 8; j := 2; next[8] := 2;12: i=8,j=2: i := 9; j := 3; next[9] := 3;

Sau khi đã xác định được mảng next chúng ta bắt đầu xét đến thuật toán:

Algorithm 3 Thuật toán Knuth-Morris-Prattfunction: kmpsearch : integer;1: var i, j : integer;2: begin3: i := 1; j := 1; initnext;4: repeat5: if (j = 0) or (a[i] = p[j]) then6: i := i + 1; j := j + 1;7: else8: j := next[j];9: end if10: until (j > M) or (i > N)11: if j > M then12: kmpsearch := i − M ;13: else14: kmpsearch := i;15: end if16: end;

Tiếp tục nghiên cứu về thuật toán bằng cách đi sâu vào từng ví dụ cụ thể.

Ví dụ 1:Trở lại với ví dụ tìm mẫu 10100111 trong một chuỗi văn bản nhị phân (với mảng

next được tính như trên):Bắt đầu so sánh từ đầu mẫu với đầu văn bản (i = j = 1) khi có sự gối khớp giữa

các ký tự (a[i] = p[j]) ta tăng cả i và j lên một. Khi a[i] <> p[j] ta giữ nguyêni và gán lại j := next[j] và so sánh lại a[i] (i như cũ) với p[j] (j được gán giá trịmới). Trong trường hợp xảy ra trường hợp j := next[1] = 0 ta không so sánh ngay

Page 12: Cac Giai Thuat Ve Xau Ky Tu

1.3. Thuật toán Knuth-Morris-Pratt 9

Hình 1.6: Ví dụ 1 - Thuật toán Knuth-Morris-Pratt.

mà tăng đồng thời i và j lên một rồi mới tiếp tục so sánh. Quá trình trên cứ tiếptục cho đến khi tìm thấy mẫu trong văn bản (j > M ) hay đã kết thúc văn bản (i > N ).

Ví dụ 2:Tìm kiếm một ký tự ababac. Đầu tiên ta tiến hành tính mảng next:

Result:1: i=1,j=0: i := 2; j := 1; next[2] := 1;2: i=2,j=1: j := next[1] = 0;3: i=2,j=0: i := 3; j := 1; next[3] := 1;4: i=3,j=1: i := 4; j := 2; next[4] := 2;5: i=4,j=2: i := 5; j := 3; next[5] := 3;6: i=5,j=3: i := 6; j := 4; next[6] := 4;7: i=6,j=4: j := next[4] = 2;8: i=6,j=2: j := next[2] = 1;9: i=6,j=1: j := next[1] = 0;10: i=6,j=0: i := 7; j := 1; next[7] := 1;

Và khi quá trình tìm kiếm được thực hiện:

Hình 1.7: Ví dụ 2 - Thuật toán Knuth-Morris-Pratt.

Ví dụ 3:Một ví dụ khác của thuật toán sẽ cho ta thấy nếu như trong mẫu các ký tự từng

đôi một khác nhau thì Knuth-Morris-Pratt sẽ không khác gì so với Brute-Force. Khi

Page 13: Cac Giai Thuat Ve Xau Ky Tu

1.3. Thuật toán Knuth-Morris-Pratt 10

đó next[1] = 0 còn next[j] = 1 với mọi j > 1.

Hình 1.8: Ví dụ 3 - Thuật toán Knuth-Morris-Pratt.

1.3.3 Những nhận xét về thuật toán

Trở lại với ví dụ 1 trên ta thấy ràng so với thuật toán Brute-Force ví dụ này dùng ítphép so sánh hơn. Tuy nhiên trong nhiều ứng dụng thực tế thuật toán Knuth-Morris-Pratt nhanh hơn không đáng kể so với thuật toán Brute-Force, do ít khi sảy ra trườnghợp tìm kiếm các mẫu có tính tự lặp lại cao trong các văn bản cũng có tính lặp lạicao.

Mặc dù vậy nhưng phương pháp này có một giá trị thực tế quan trọng là: Ta cóthể tiến hành tìm kiếm tuần tự trong văn bản và không bao giờ phải dự phòng trongvăn bản đó. Điều này rất có ý nghĩa khi áp dụng trên một tệp tin lớn được đọc từ mộtthiết bị ngoại vi nào đó thì các thuật toán yêu cầu dự phòng sẽ tiêu tốn bộ đệm hơn(buffering).

Page 14: Cac Giai Thuat Ve Xau Ky Tu

1.4. Thuật toán Boyer-Moore 11

Tính chất 1.3.1Thuật toán Knuth-Morris-Pratt không bao giờ dùng nhiều hơn M + N phép so sánhký tự.

Tính chất này được minh họa trong các ví dụ trên. Điều này cũng có thể thấyđược qua thuật toán là: Hoặc là ta tăng j hoặc là ta gán nó với một giá trị của mảngnext hầu như chỉ ứng với một lần cho mỗi i.

1.4 Thuật toán Boyer-Moore

1.4.1 Tư tưởng của thuật toán

Nếu việc dự phòng là không khó khăn, thì ta có thể phát triển một phương pháp tìmkiếm chuỗi nhanh hơn đáng kể bằng cách quét mẫu từ phải sang trái khi so sánh nóvới văn bản. Một ví dụ đơn giản là khi tiến hành tìm kiếm mẫu 10100111, nếu giảsử ta thấy có sự trùng khớp trên các ký tự thứ 8, thứ 7, thứ 6 nhưng không khớp ở kýtự thứ 5 thì ta có thể cho mẫu dịch ngay sang phải 7 vị trí và tiếp tục so sánh. Sở dĩta làm được điều này là vì có một nhận xét tưởng nhưng đơn giản nhưng có ý nghĩathực tế lớn đó là chuỗi 111 không xuất hiện ở bất cứ đâu khác trong mẫu.

Hình 1.9: Minh họa tư tưởng của thuật toán Boyer-Moore.

Với cách suy luận như vậy ta có thể xây dựng một mảng next đi từ phải sang tráicho thuật toán này tương tự như Knuth-Morris-Pratt. Tuy nhiên chúng ta sẽ khôngđi xâu nghiên cứu theo cách này vì có một cách hoàn toàn khác để nhảy qua (skip)các ký tự với việc quét mẫu từ phải sang trái tốt hơn hẳn trong nhiều trường hợp.

1.4.2 Phát biểu thuật toán và các ví dụ

Chúng ta bắt đầu nghiên cứu thuật toán này thông qua ví dụ đầu tiên.

Ví dụ 1:Tìm kiến chuỗi STING trong văn bản.Ta tiến hành so sánh từ phải sang trái, đầu tiên so sánh ký tự G trong mẫu với ký

tự thứ 5 trong văn bản là R. Ta nhận thấy sự không khớp ở đây và có thêm được mộtnhận xét quan trọng là R không xuất hiện ở bất cứ chỗ nào trong mẫu, như vậy ta cóthể dịch mẫu quaR (dịch đi 6 vị trí).Ta so sánh tiếpG với S (ký tự thứ 10 trong mẫu),

Page 15: Cac Giai Thuat Ve Xau Ky Tu

1.4. Thuật toán Boyer-Moore 12

Hình 1.10: Ví dụ 1 - Thuật toán Boyer-Moore.

lại xảy ra sự không khớp, nhưng S lại có xuất hiện trong mẫu vì vậy ta dịch mẫu đi 5vị trí tức là cho đến khi ký tự S trong văn bản khớp với ký tự S trong mẫu. Quá trìnhtrên tiếp diễn tương tự cho đến khi so sánh G với ký tự T trong CONSISTING.Có sự không trùng khớp, nhưng T lại có xuất hiện trong mẫu nên ta dịch mẫu đi 4 vịtrí cho đến khi nó trùng với ký tự T trong mẫu. Và ở vị trí mới ta tìm được một sựtrùng khớp hoàn toàn của mẫu trong văn bản. Với phương pháp này đưa ta đến kếtquả chỉ sau 7 bước so sánh (và thêm 5 lần so sánh các ký tự trong STING với vănbản nữa để chỉ ra sự trùng khớp hoàn toàn).

Về phương pháp cái đặt thuật toán "ký tự không khớp" (mismatched-character)này là tương đối dễ dàng. Đơn giản là nâng cấp thuật toán Bruce-Force để quét mẫutừ phải sang trái bằng cách sử dụng một mảng skip mà nó cho biết với mối ký tựtrong mẫu ta phải nhảy đi bao nhiêu ký tự nếu nó xuất hiện trong văn bản và gây ramột sự không ăn khớp khi tiến hành tìm kiếm chuỗi.

Đầu tiên xây dựng một function index(c : char) : integer; mà nó sẽ trả về 0 đốivới các khoảng trắng và i đối với ký tự thứ i của bảng chữ cái. Ta cũng xây dựngmột thủ tục procedure initskip; nó cho biết giá trị của mảng skip là M đối với cácký tự không có trong mẫu và sau đó cho j chạy từ 1 đến M đặt skip[index(p[j])] làM − j.

Algorithm 4 Hàm indexfunction: index(c : char) : integer;1: var i : integer;2: begin3: i := 1;4: index := 0;5: if c <>′ ′ then6: repeat7: if c = p[i] then8: index := i; i := i + 1;9: end if10: until (i > M)11: end if12: end;

Page 16: Cac Giai Thuat Ve Xau Ky Tu

1.4. Thuật toán Boyer-Moore 13

Ta có một nhận xét là hàm index chỉ trả về các giá trị từ 0 đến M , do đó chỉ sốcủa mảng skip cũng chỉ nhận giá trị từ 0 đến M . Cụ thể là 0 cho mọi ký tự của vănbản không nằm trong mẫu, ngược lại nếu một ký tự đang xét trong văn bản có nằmtrong mẫu thì sẽ được gán với một chỉ số tương ứng từ 1 đến M , trường hợp nếu cóhai ký tự giống nhau trong mẫu thì lấy chỉ số của ký tự nằm bên phải nhất.

Algorithm 5 Tính mảng skipprocedure: initnext;1: var j : integer;2: begin3: for j := 0 to M do4: skip[j] := M ;5: end for6: for j := 1 to M do7: skip[index(p[j])] := M − j;8: end for9: end;

Quay lại ví dụ trên ta được kết quả như sau: skip cho S là 4, skip cho T là 3,skip cho I là 2, skip cho N là 1 và skip cho G là 0.

Algorithm 6 Thuật toán Boyer-Moorefunction: mischarsearch : integer;1: var i, j : integer;2: begin3: i := M ; j := M ; initskip;4: repeat5: if a[i] = p[j] then6: i := i − 1; j := j − 1;7: else8: i := i + M − j + 1; j := M ;9: if skip[index(a[i])] > M − j + 1 then10: i := i + skip[index(a[i])] − (M − j + 1);11: end if12: end if13: until (j < 1) or (i > N)14: mischarsearch := i + 1;15: end;

Lại kiểm tra với ví dụ 1 ở dòng 2 khi i = 5, j = 5 thì M − j + 1 = 5− 5 + 1 = 1nhỏ hơn skip của R là bằng 5 (vì R không xuất hiện trong mẫu), nên i := 5 + 5 =10; j := 5; để chuyển sang dòng 3 so sánh G với S. Khác biệt ở dòng 3 là skip

của S bằng 4 nên mẫu chỉ dịch sang phải 4 vị trí sao cho vị trí của S ứng vớij = 1 trong mẫu trùng khớp S ứng với i = 10 trong văn bản. Tương tự cho cácdòng còn lại cho đến khi trùng khớp hoàn toàn hoặc không tìm thấymẫu trong văn bản.

Page 17: Cac Giai Thuat Ve Xau Ky Tu

1.4. Thuật toán Boyer-Moore 14

Ví dụ 2:

Hình 1.11: Ví dụ 2 - Thuật toán Boyer-Moore.

Ta xét đến một trường hợp khác với ví dụ trên: Đó là ở bước 4, sau khi đã tìmđược một chuỗi các ký tự trùng khớp là MPLE, khi đó i = 12 (ở vị trí của I). Tanhận thấy rằng I không xuất hiện trong mẫu nên skip của I là bằng 7 (bằng M ) dođó ta gán lại i := 12 + 7 = 19; j := M = 7; điều này là tương đương với dịch mẫusang phải 3 vị trí để tiếp tục bước 5 so sánh E với X .

Ví dụ 3:Tương tự như đã biện luận với hai ví dụ trên, chúng ta tiếp tục xét đến ví dụ 3 qua

hình sau để hiểu thêm về thuật toán.

Hình 1.12: Ví dụ 3 - Thuật toán Boyer-Moore.

Page 18: Cac Giai Thuat Ve Xau Ky Tu

1.5. Thuật toán Rabin-Krap 15

1.4.3 Những nhận xét về thuật toán

Tính chất 1.4.1Thuật toán Boyer-Moore không bao giờ dùng nhiều hơn M + N phép so sánh ký tựvà dùng khoảng N/M bước nếu văn bản không nhỏ và mẫu khong dài.

Thuật toán là tuyến tính trong trường hợp xấu nhất là theo cùng cách cài đặt nhưKnuth-Morris-Pratt (với cách cài đặt như trên thì Boyer-Moore là không tuyến tính).Kết quả N/M có thể được chứng minh trong các chuỗi ngẫu nhiên khác nhau, tuynhiên các mô hình này có vẻ không thực tế do đó ta sẽ bỏ qua phần trình bày chi tiết.Trong nhiều trường hợp thực tế, khi mà các ký tự của văn bản không xuất hiện trongmẫu hoặc ngoại trừ một số ít là có mặt trong mẫu, do đó mỗi phép so sánh dẫn tớimẫu sẽ dịch sang phải M ký tự và điều đó dẫn đến kết quả đã được phát biếu trên.

Thuât toán Boyer-Moore rõ ràng không giúp ích gì nhiều cho các chuỗi nhị phân,vì chỉ có hai khả năng để cho các ký tự trở thành không khớp (0 hoặc 1) và cả hai khảnăng này đều có thể nằm trong mẫu. Tuy nhiên các bit có thể nhóm lại với nhau đểtao nên các ký tự chữ cái và có thể dùng như trên. Nếu ta lấy b bits ở một thời điểmthì ta cần một bảng skip với 2b đầu vào. Giá trị của b sẽ được chọn đủ nhỏ để chobảng này không quá lớn nhưng đủ lớn để cho hầu hết các đoạn b − bit của văn bảnkhông nằm trong mẫu. Đặc biệt, có M − b + 1 đoạn b − bit khác nhau trong mẫu(mỗi đoạn bắt đầu ở mỗi vị trí từ 1 đến M − b + 1), như thế ta muốn M − b + 1 nhỏhơn đáng kể so với 2b. Ví dụ, nếu ta lấy b là khoảng lg(4M), thì bảng skip sẽ có hơn3/4 được đổ đầy với M đầu vào, b cũng phải nhỏ hơn M/2, vì nếu không ta có thểbỏ sót toàn bộ mẫu nếu nó bị tách ra giữa hai đoạn văn bản b − bit.

1.5 Thuật toán Rabin-Krap

1.5.1 Tư tưởng của thuật toán

Để hiểu hơn về thuật toàn này trước hết ta nhắc lại một chút về phép băm (hashing):đó là một phương pháp tham chiếu trực tiếp đến các mẩu tin bằng cách thực hiệncác phép chuyển đổi số học từ khóa vào các địa chỉ bảng. Chúng ta giả sử rằng cáckhóa là các số nguyên phân biệt từ 1 đến N thì chúng ta có thể lưu mẩu tin với khóai trong vị trí i của bảng, và chuẩn bị đẻ truy xuất tức thời nhờ vào giá trị của khóa.Phép băm là sự tổng quát hóa của phương pháp tầm thường này cho các ứng dụngtìm kiếm thông thường khi chúng ta không có sự hiểu biết đặc biệt về khóa (chưa cóthông tin cụ thể về các giá trị khóa).

Bước đầu tiên của việc tìm kiếm bằng phép băm là tính một hàm băm (hashfunction) để chuyển đổi từ khóa tìm kiếm vào địa chỉ bảng. Trường hợp lý tưởng,các khóa khác nhau sẽ ánh xạ vào các địa chỉ khác nhau, nhưng thực tế sẽ không có

Page 19: Cac Giai Thuat Ve Xau Ky Tu

1.5. Thuật toán Rabin-Krap 16

hàm băm hoàn chỉnh và sẽ có hai hay nhiều khóa khác nhau sẽ băm đến cùng mộtđịa chỉ. Bước thứ hai là giải quyết các xung đột (collision-resolution) đối với trườnghợp như đã nói ở trên. Một trong các phương pháp để giải quyết xung đột mà chúngta nghiên cứu là dùng các danh sách liên kết, bởi vì là lưu trữ động nên phương phápnày rất thích hợp khi số lượng khóa tìm kiếm không thể đoán định trước. Hai phươngpháp xử lý xung đột khác sẽ có thời gian tìm kiếm nhanh hơn trên các mẩu tin đượclưu trữ trong một mảng cố định mà các bạn sẽ tự tìm hiểu thêm.

Trở lại với thuật toán Rabin-Karp là một cách tiếp cận Brute-Force cho việc tìmkiếm chuỗi mà ta đã không xem xét ở trên là khai thác một vùng nhớ lớn bằng cáchxemmỗi đoạnM ký tự có thể có của văn bản như là khóa 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 saocho chỉ một khóa là đang được tìm kiếm; việc mà ta cần làm là tính hàm băm choM ký tự từ văn bản và kiểm tra xem chúng có bằng với mẫu hay không. Rabin vàKarp đã tìm ra một phương pháp dễ dàng để giải quyết trở ngại này với hàm bămsau: h(k) = k mod q, ở đây q (kích thước bảng) là một số nguyên tố lớn.

Hình 1.13: Minh họa tư tưởng của thuật toán Rabin-Krap.

Phương pháp này dựa trên việc tính hàm băm cho vị trí thứ i trong văn bản, chotrước giá 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ử 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ừ (world),mà ta xem nó như là một số nguyên. Điều này ứng với việc ghi lại các ký tự như cáccon 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ớia[i...i + M − 1] là:

x = a[i]dM−1 + a[i + 1]dM−2 + ... + a[i + M − 1]

và có thể giả sử rằng h(x) = x mod q. Nhưng dịch sang phải một vị trí trongvăn bản tương ứng với việc thay x bởi:

(x − a[i]dM−1)d + a[i + M ]

Page 20: Cac Giai Thuat Ve Xau Ky Tu

1.5. Thuật toán Rabin-Krap 17

Một tính chất cơ bản của phép toán mod là có thể thực hiện nó ở bất ký chỗnào trong các phép toán mà vẫn nhận được cùng một kết quả. Nói cách khác là nếuta lấy phần dư khi chia cho q sau mỗi phép toán số học (để cho các số mà ta gặp luônlà nhỏ) thì sẽ nhận được kết quả tương đương với việc thực hiện tất cả các phép toánsố học, sau đó mới lấy phần dư khi chia cho q. Chính điều này đã dẫn tới một thuậttoán đối sánh mẫu rất đơn giản mà ta sẽ nghiên cứu việc cài đặt ngay sau đây.

1.5.2 Phát biểu thuật toán và các ví dụ

Chương trình sau giả sử dùng hàm index như trong thuật toán Boyer-Moore, nhưngdùng d = 32 để cho hiệu quả (các phép nhân có thẻ được đặt như các phép dịch bit).

Algorithm 7 Thuật toán Rabin-Krapfunction: rksearch : integer;1: const q = 33554393; d = 32;2: var h1, h2, dM, i : integer;3: begin4: dM := 1;5: for i := 1 to M − 1 do6: dM := (d ∗ dM) mod q;7: end for8: h1 := 0;9: for i := 1 to M do10: h1 := (h1 ∗ d + index(p[i])) mod q;11: end for12: h2 := 0;13: for i := 1 to M do14: h2 := (h2 ∗ d + index(a[i])) mod q;15: end for16: i := 1;17: while (h1 <> h2) and (i <= N − M) do18: h2 := (h2 + d ∗ q − index(a[i]) ∗ dM) mod q;19: h2 := (h2 ∗ d + index(a[i + M ])) mod q;20: i := i + 1;21: end while22: rksearch := i;23: end;

Đầu tiên tính giá trị dM−1 mod q trong biến dM , sau đó tới giá trịh1 cho mẫuvà tính cả giá trị h2 cho M ký tự đầu tiên của văn bản. Sau đó chương trình dùngđến kỹ thuật như đã nói ở trên để băm cho M ký tự trong văn bản, bắt đầu từ ký tựở vị trí thứ i đối với mỗi i và so sánh từng giá trị băm mới với h1. Số nguyên tố qđược chọn càng lớn càng tốt, nhưng cũng phải đủ nhỏ sao cho (d + 1) ∗ q khônggây ra tràn so với kiểu integer (overflow): điều này cần ít phép toán mod hơn

Page 21: Cac Giai Thuat Ve Xau Ky Tu

1.5. Thuật toán Rabin-Krap 18

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ợ đượccộng vào thêm khi tính h2 đảm bảo rằng mọi đại lượng vẫn còn là đương để chophép toán mod có thể thực hiện được).

Ví dụ 1:

Hình 1.14: Ví dụ 1 - Thuật toán Rabin-Krap.

Quá trình tìm kiếm diễn ra bằng cách so sánh giá trị băm của mỗi một dãy M kýtự trong văn bản với giá trị băm của mẫu, nếu không bằng nhau mẫu sẽ dịch sangphải một vị trí so với văn bản và tiếp tục so sánh. Kết quả là tìm thấy mẫu trong vănbản khi có hai giá trị băm là bằng nhau.

1.5.3 Những nhận xét về thuật toán

Tính chất 1.5.1Phép đối sánh mẫu Rabin-Krap gần như là tuyến tính.

Số phép so sánh theo thuật toán này hiển nhiên 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ùng giá trị băm với mẫu. Đểchắc chắn, ta nên tiến hành so sánh trực tiếp văn bản đó với mẫu. Tuy nhiên, việcsử dụng giá trị rất lớn của q và không duy trì một mảng băm thực sự làm cho phéptính mod rất khó xảy ra trường hợp hai giá trị khác nhau có cùng một giá trị băm.

Page 22: Cac Giai Thuat Ve Xau Ky Tu

1.6. Đa truy 19

Về mặt lý thuyết thuật toán này vẫn có thể cần đến M × N bước trong trường hợpxấ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ựchiện việc tìm kiếm chuỗi gồm khoảng M + N bước.

1.6 Đa truy

Tất cả các bài toán đã bàn tới đều hướng tới một bài toán tìm kiếm chuỗi cụ thể: tìmsự xuất hiện của một mẫu cho trước trong một văn bản cho trước. Nếu cùng một vănbăn là đối tượng của nhiều phép tìm kiếm mẫu, thì sẽ thực hiện một cách khôn ngoanmột xử lý nào đó trên chuỗi để cho phép tìm kiếm kế tiếp là hiệu quả.

Nếu có một số lượng lớn các phép tìm kiếm, thì bài toán tìm kiếm chuỗi có thểđược xem như một trường hợp đặc biệt của bài toán tìm kiếm tổng quát. Đơn giản làxem văn bản như là N "khóa" gối nhau, khóa thứ i sẽ được định nghĩ là a[1..N ],toàn thể chuỗi văn bản bắt đầu ở vị trí i. Dĩ nhiên, ta không thao tác trên chính cáckhóa đó mà trên những con trỏ tới chúng: khi cần so sánh các khóa i và j, ta thựchiện các phép so sánh "từng ký tự một" bắt đầu ở các vị trí i và j trong chuỗi vănbản. Sau đó có thể dùng trực tiếp các thuật toán băm, cây nhị phân và các thuật toánkhác. Trước tiên, xây dựng một cấu trúc toàn cục từ chuỗi văn bản, sau đó có thểthực hiện các phép tìm kiếm hiệu quả đối với các mẫu cụ thể.

Nhiều chi tiết cần thiết sẽ được "xem xét qua" trong việc áp dụng các thuật toántìm kiếm vào việc tìm kiếm chuỗi theo phương pháp này; chùng ta sẽ chỉ ra điều nàynhư một chọn lựa phù hợp cho vài ứng dụng tìm kiếm chuỗi. Những phương phápkhác nhau sẽ thích hợp trong các trường hợp khác nhau. Lấy ví dụ, nếu các phéptìm kiếm áp dụng cho các mẫu có cùng độ dài, thì một mảng băm được kiến tạo vớimột lần quét duy nhất, giống như thuật toán Rabin-Karp, sẽ cho thời gian tìm kiếmlà không đổi tính theo trung bình. Mặt khác, các mẫu có độ dài biến thiên, thì mộttrong các phương pháp dựa vào cây (tree-based) có thể thích hợp (Patricia đặc biệtthích hợp cho một ứng dụng như vậy).

Các biến thể khác trong bài toàn có thể khiến cho nó trở nên khó hơn đáng kể vàdẫn tới các phương pháp khác nhau rất nhiều, như ta sẽ thấy trong chương kế tiếp.

Page 23: Cac Giai Thuat Ve Xau Ky Tu

Chương 2

Đối sánh mẫu

Thường thì ta muốn tìm kiếm chuỗi với một thông tin ít hơn thông tin hoàn chỉnh vềmẫu sẽ được tìm. Ví dụ, khi sử dụng một trình soạn thảo văn bản người ta có thể chỉmuốn đưa ra một phần của một mẫu, hay chỉ ra một mẫu có khả năng trùng khớp ởmột số ít từ khác nhau, hay lại chỉ ra rằng bất ký một thể hiện nào bao gồm một vàiký tự sẽ được bỏ qua. Trong chương này chúng ta sẽ xem xét làm thế nào để có thểthực hiện phép đối sánh mẫu kiểu này một cách hiệu quả.

Các thuật toán trong chương trước có một sự phụ thuộc cơ bản vào đặc tả hoànchỉnh của mẫu, vì vậy ta phải xem xét các phương pháp khác. Các cơ chế căn bảnmà ta sẽ xem xét có khả năng tạo ra một phương tiện tìm kiếm chuỗi rất mạnh, cókhả năng đối sánh các mẫu M ký tự phức tạp trong các văn bản N ký tự có số bướctỉ lệ với M × N trong trường hợp xấu nhất, nhanh hơn nhiều với những ứng dụngđặc thù.

Đầu tiên, ta phát triển một phương pháp để mô tả các mẫu: một "ngôn ngữ" có thểsử dụng để chỉ ra theo một cách nghiêm ngặt, các kiểu bài toán tìm kiếm chuỗi đượcphát biểu ở trên. Ngôn ngữ này sẽ bao hàm các phép toán "nguyên sơ" (primitiveoperations), mạnh hơn phép toán đơn giản là "kiểm tra xem ký tự thứ i của văn bản cókhớp với ký tự thứ j của mẫu không" được dùng trong chương trước. Trong chươngnày ta sẽ xem xét ba phép toán cơ bản theo cách nói của một kiểu máy tưởng tượngcó thể tìm các mẫu trong một văn bản. Thuật toán đối sánh mẫu của chúng ta sẽ làmột phương pháp để giả lập phép toán của kiểu máy này. Trong phạm vi tài liệu nàysẽ không nói rõ về việc làm thế nào để dịch từ đặc tả mẫu mà người dùng khai thác,mô tả công việc tìm chuỗi thành đặc tả máy mà thuật toán khai thác để thực thi việctìm kiếm, các bạn có thể tìm hiểu thêm sau.

Như sẽ thấy lời giải mà ta phát triển đối với bài tòan đối sánh mẫu có liên hệ mộtcách mật thiết với các tiến trình cơ sở trong khoa học máy tính. Ví dụ, phương phápta sẽ dùng trong chương trình đêt thực hiện công việc tìm kiếm chuỗi suy ra từ mô tảmẫu cho trước là cùng loại với phương pháp được dùng bởi hệ Pascal để thực hiệncông việc tính toán có liên quan đến một chương trình Pascal cho trước.

Page 24: Cac Giai Thuat Ve Xau Ky Tu

2.1.Mô tả mẫu 21

2.1 Mô tả mẫu

Ta sẽ xét các mô tả mẫu tạo ra bởi các ký hiệu, ràng buộc với nhau bởi ba phép toánsau đây:

• Phép nối (concatenation): Đây là thao tác đã được dùng trong chương trước.Nếu hai ký tự kề nhau trong mẫu, thì có một sự trùng khớp xảy ra nếu và chỉnếu hai ký tự trong văn bản giống với nó cũng nằm kề nhau. Ví dụ, AB hiểulà A được theo sau bởi B.

• Phép hội (or): Đây là thao tác cho phép chúng ta chỉ các trường hợp trongmẫu. Nếu ta có một phép OR giữa hai ký tự, thì có một sự trùng khớp nếuvà chỉ nếu một trong các ký tự đó xuất hiện trong văn bản. Ta sẽ ký hiệuthao tác này bằng cách dùng dấu + và dùng dấu ngoặc đơn để tổ hợp nó vớiphép nối theo một cách thức phức tạp tùy ý. Ví dụ như, A + B nghĩa là"hoặc A hay B"; C(AC + B)D nghĩa là "hoặc CACD hoặc CBD"; và(A + C)((B + C)D) nghĩa là "hoặc ABD hoặc CBD hoặc ACD hoặcCCD".

• Phép kết (closure): Thao tác này cho phép các phần của một mẫu sẽ được lặplại một cách tùy ý. Nếu ta có phép kết của một ký hiệu, thì sẽ có một sự trùngkhớp xảy ra nếu và chỉ nếu ký hiệu đó xảy ra trong văn bản trong văn bản vớisố lần tùy ý (kể cả 0). Phép kết sẽ được ký hiệu bởi việc đặt dấu ∗ sau ký tựhay nhóm ký tự được bao bởi dấu ngoặc đơn sẽ được nhắc lại. Ví dụ, AB∗ đốisánh các chuỗi gồm có một chữ A theo sau là một dãy tùy ý các chữ B, trongkhi (AB)∗ sẽ đối sánh các chuối gồm các chữ A và B đan xen nhau.

Một chuỗi các ký hiệu được xây dựng bằng cách dùng đến ba phép toán này đượcgọi là một biểu thức chính quy (regular expression). Mỗi biểu thức chính quy có thểxác định các mẫu văn bản đặc thù. Mục đích của chúng ta là phát triển một thuậttoán xem có bất kỳ mẫu nào được mô tả bởi một biểu thức chính quy cho trước cóxuất hiện trong một văn bản cho trước hay không.

Ta sẽ tập trung vào phép nối, phép hội và phép kết theo thứ tự để minh họa cácnguyên lý cơ bản trong việc phát triển một thuật toán đối sánh mẫu theo biểu thứcchính quy. Các bổ sung khác để cho tiện sẽ được tạo trong các hệ thống thực sự. Vídụ, −A có thể hiểu là "trùng khớp bất kỳ ký tự nào ngoại trừ A". Phép toán NOTnày giống như một phép toán OR ám chỉ tất cả các ký tự ngoại trừ A, nhưng dễdùng hơn nhiều. Tương tự, "?" có thể hiểu là "trùng khớp bất ký ký tự nào". Nhữngví dụ khác về các ký hiệu bổ sung để tạo ra đặc tả các mẫu lớn dễ dàng hơn, đó làcác ký hiệu đối sánh nơi bắt đầu hay kết thúc của một dòng, bất kỳ ký tự nào hay consố nào,...

Page 25: Cac Giai Thuat Ve Xau Ky Tu

2.2. Các máy đối sánh mẫu 22

Các phép toán này có khả năng biểu đạt rất đáng kể. Ví dụ như, mô tả mẫu?∗(ie + ei)?∗ sẽ tương đương với tất cả các từ có ie hay ei; (1 + 01)∗(0 + 1) môtả tất cả các chuối số của 0 và 1, không có hai số 0 kề nhau. Hiển nhiên là có nhiềucách mô tả khác nhau cùng cho một chuỗi: ta phải cố xác định mô tả ngắn gọn để cóđược các thuật toán hiệu quả.

Thuật toán đối sành mẫu mà ta sẽ xem xét có thể xem như là một sự khái quáthóa thuật toán Brute-Force từ trái sang phải. Thuật toán tìm kiếm chuỗi con trái nhấttrong văn bản mà nó khớp với mẫu mô tả bằng cách quét chuỗi văn bản từ trái sangphải, kiểm tra ở mỗi vị trí xem khi nào thì có một chuỗi con bắt đầu vị trí đó khớpvới mẫu mô tả.

2.2 Các máy đối sánh mẫu

Thuật toán Knuth-Morris-Pratt có thể xét đến như một máy trạng thái hữu hạn đượckiến tạo từ mẫu tìm kiếm mà nó quét trên văn bản. Phương pháp ta sẽ sử dụng để đốisánh mẫu biểu thức chính quy là một sự khái quát hóa của thuật toán này.

Máy trạng thái hữu hạn cho thuật toán Knuth-Morris-Pratt thay đổi từ trạng tháinày sang trạng thái khác bằng cách nhìn vào một ký tự của chuỗi văn bản và sau đóchuyển đổi thành một trạng thái nào đó nếu có sự trùng khớp, nếu không thì thànhmột trạng thái khác. Một sự không trùng khớp ở bất kỳ một chỗ nào có nghĩa làmẫu không thể xuất hiện trong văn bản ở bắt đầu ở điểm đó. Bản thân thuật toáncó thể được xem như là sự mô phỏng cho một cái máy. Đặc trưng mà nó khiến chodễ dàng mô phỏng là tính tất định (deterministic): mỗi chuyển tiếp (transition) trạngthái hoàn toàn được xác định bởi ký tự nhập kế tiếp.

Để kiểm soát các biểu thức chính quy, cần xem xét một máy trừu tượng mạnh hơn.Do phép toán OR, máy không thể xác định được khi nào thì mẫu có thể xuất hiện ởmột vị trí cho trước bằng cách chỉ kiểm tra một ký tự: thực vậy, do phép toán kết,không thể xác định có bao nhiêu ký tự cần phải kiểm tra trước khi phát hiện ra một sựbất đối sánh. Cách tự nhiên nhất để vượt qua các vấn đề này là trang bị cho máy sứcmạnh của tính bất định (nondeterminism): khi có nhiều các để đối sánh mẫu, máy sẽ"đoán ra" cái đúng! Phép toán này có vẻ như không khả thi, nhưng ta sẽ thấy là dễdàng viết một chương trình để mô phỏng các hành động của một máy như vậy.

Hình trên minh họa một cái máy bất định trạng thái hữu hạn có thể được sử dụngđể tìm mẫu mô tả (A∗B + AC)D trong một chuỗi văn bản. Các trạng thái đượcđánh số theo một phương pháp sau đây. Máy này có thể chuyển từ một trạng tháiđược gán nhãn bởi một ký tự thành trạng thái "được trỏ tới" bởi trạng thái đó bằngcách so khớp (và quét qua) ký tự trong chuỗi văn bản. Những gì mà nó tạo ra tính bấtđịnh của máy là một vài trạng thái (được gọi là các trạng thái trống hayNULL) mà

Page 26: Cac Giai Thuat Ve Xau Ky Tu

2.2. Các máy đối sánh mẫu 23

Hình 2.1: Một máy nhận diện mẫu bất định cho (A∗B + AC)D.

nó không được gán nhãn, mà còn có thể "trỏ tới" hai trạng thái kế tiếp (successor)khác nhau. Một vài trạng thái như trạng thái 4 trong hình vẽ là các trạng thái "no-op"với một ngõ ra mà nó không ảnh hưởng đến thao tác của máy nhưng làm thuận tiệnhơn cho việc cài đặt của chương trình kiến tạo máy, như ta sẽ thấy sau. Trạng thái9 là một trạng thái NULL không có ngõ ra, để ngừng máy lại. Khi ở trong mộttrạng thái như vậy, máy có thể đi tới một trạng thái kế tiếp bất chấp ngõ vào (khôngquét qua bất kỳ cái gì). Máy có khả năng đoán được chuyển tiếp nào sẽ dẫn tới mộtsự trùng khớp đối với chuỗi văn bản cho trước (nếu có). Chú ý là không có nhữngchuyển tiếp "không khớp" như trong máy trước: máy không tìm ra một sự trùng khớpnếu nó nó không có đường đi, ngay cả khi đoán ra một dãy các chuyển tiếp dẫn tớimột sự trùng khớp.

Máy có một trạng thái khởi đầu và một trạng thái kết thúc duy nhất. Sau khi đượckhởi động trong trạng thái khởi đầu, máy có khả năng nhận diện bất kỳ một chuỗinào được mô tả bằng cách đọc các ký tự và thay đổi các trạng thái, tương ứng vớicác quy luật của nó và hoàn tất ở trạng thái kết thúc. Do máy có khả năng bất định,nó có thể đoán được dãy các thay đổi về trạng thái có thể dẫn tới lời giải. Ví dụ,để xác định xem mẫu mô tả (A∗B + AC)D có thể xảy ra trong chuỗi văn bảnCDAABCAAABDDACDAAC hay không, máy sẽ ngay lập tức thông báolỗi khi được khởi động trên ký tự thư nhất hay thứ hai; nó sẽ làm một cái gì đó đểthông báo lỗi trên các ký tự thứ năm và thứ sáu; và nó sẽ đoán được ra dãy các chuyểntiếp trạng thái 5 2 2 1 2 1 2 3 4 8 9 để nhận diệnAAABDnếu được khởi động trên ký tự thư bảy.

Chúng ta có thể kiến tạo một máy cho một biểu thức chính quy cho trước bằngcách xây dựng các máy bộ phận cho các phần của biểu thức và định nghĩa các phươngpháp trong đó hai máy bộ phận có thể được tổ hợp lại thành một máy lớn hơn chomỗi một phép toán trong số ba phép toán trên: phép nối. phép hội và phép kết.

Ta bắt đầu với một máy phụ để nhận diện một ký tự cụ thể. Thuận lợi khi viết nónhư một máy hai trạng thái, với một trạng thái khởi đầu (mà nó cũng là ký tự nhận

Page 27: Cac Giai Thuat Ve Xau Ky Tu

2.2. Các máy đối sánh mẫu 24

diện) và một trạng thái kết thúc, như được minh họa trong hình sau.

Hình 2.2: Máy hai trạng thái để nhận diện một ký tự.

Bây giờ để tạo máy cho phép nối hai biểu thức từ các máy cho các biểu thức riênglẻ, đơn giản là trộn hai trạng thái kết thúc của máy thứ nhất với trạng thái khởi đầucủa máy thứ hai, hình sau sẽ nói lên điều này.

Hình 2.3: Kiến tạo máy trạng thái: phép nối.

Tương tự , máy cho phép toán hội OR được xây dựng bằng cách thêm một trạngthái NULL mới trỏ tới hai trạng thái khởi đầu và một trạng thái kết thúc sẽ trỏ vàomột trạng thái kết thúc còn lại, mà nó trở thành trạng thái kết thúc của máy được tổhợp, như trong hình sau.

Hình 2.4: Kiến tạo máy trạng thái: phép hội.

Cuối cùng, máy cho phép toán kết được xây dựng bằng cách làm cho trạng tháikết thúc trở thành trạng thái khởi đầu: để cho nó trỏ ngược lại trạng thái khởi đầu cũvà một trạng thái kết thúc mới, theo dõi trong hình 2.5.

Một máy có thể được xây dựng tương ứng với bất kỳ biểu thức chính quy nàobằng cách áp dụng liên kết các quy tắc này. Các trạng thái cho máy ví dụ trên đượcđánh số theo thứ tự khởi tạo khi máy được xây dựng bằng cách dò mẫu từ trái sangphải, như thế việc kiến tạo của máy từ các quy tắc ở trên có thể lần theo một cách dễdàng. Chú ý rằng chúng ta có một máy phụ hai trạng thái cho mỗi ký tự trong biểu

Page 28: Cac Giai Thuat Ve Xau Ky Tu

2.3. Biểu diễn máy 25

Hình 2.5: Kiến tạo máy trạng thái: phép kết.

thức chính quy và mỗi dấu + hay ∗ sẽ khiến cho một trạng thái được khởi tạo (phépnối khiến cho một trạng thai bị xóa), như thế trạng thái chắc chắn sẽ ít hơn hai lần sốký tự trong biểu thức chính quy.

2.3 Biểu diễn máy

Tất cả các máy bất định của chúng ta sẽ được kiến tạo bằng cách chỉ dùng ba quy tắckết hợp đã được phác thảo ở trên, và ta có thể lợi dụng cấu trúc đơn giản của chúngđể sử dụng chúng theo một cách thức đơn giản. Ví dụ, không có nhiều hơn hai đườngrời khỏi bất ký một trạng thái nào. Thực vậy chỉ có hai kiểu trạng thái: các trạngthái được gán nhãn bởi một ký tự từ bảng chữ cái nhập vào (với một đường ra) vàcác trạng thái không được gán nhãn (với nhỏ hơn hoặc bằng hai đường rởi khỏi nó).Điều này có nghĩa là máy có thể được biểu diễn chỉ với một vài mẩu tin trên một nút(node). Vì ta sẽ thường xuyen muốn truy xuất các trạng thái chỉ bằng con số, cách tổchức phù hợp nhất cho máy sẽ là biểu diễn theo mảng. Ta sẽ dùng ba mảng đồng thờilà character, next1 và next2 với chỉ số state để biểu diễn và truy xuất máy.Nó sẽ có khả nằng để qua được 2/3 số lượng không gian này, vì mỗi trạng thái thựcsự chỉ dùng hai thông mẩu thông tin có nghĩa, nhưng ta sẽ bỏ qua sự cải tiến này đểcho dễ hiểu và cũng vì mẫu mô tả thường là không quá dài.

Hình 2.6: Biểu diễn mảng cho máy ở hình 2.1

Máy ở hình 2.1 được biểu diễn qua hình này. Các chỉ số đầu vào được xác định

Page 29: Cac Giai Thuat Ve Xau Ky Tu

2.4.Mô phỏng máy 26

bởi state có thể được thông dịch như các lệnh với máy bất định có dạng: "Nếu bạnđang ở vị trí state và bạn thấy character[state] thì quét ký tự và đi đến trạngthái next1[state] (hay next2[state])". Trạng thái 9 là trạng thái kết thúc trongví dụ này và trạng thái 0 là trạng thái khởi đầu giả mà các điểm vào next của nó làsố hiệu của trạng thái khởi đầu thực sự.

Ta làm thế nào để tạo nên máy từ mẫu mô tả biểu thức chinh quy và làm thế nàođể các máy như vậy có thể biểu diễn như mảng. Tuy nhiên, viết một chương trìnhđể làm công việc dịch từ một biểu thức chính quy thành một biểu diễn máy bất địnhtương ứng lại là một vấn đề hoàn toán khác. Thực vậy, ngay cả viết một chươngtrình để xác định xem một biểu thức chính quy cho trước hợp lệ hay không là mộtthách đố đối với người không quen. Thao tác này được gọi là phân tích văn phạm sẽkhông trình bày trong phạm vi tài liệu này (các bạn có thể tìm hiểu thêm). Hiện tại tasẽ giả định là phép dịch này đã được thực hiện, sao cho có thể dùng được các mảngcharacter, next1 và next2 để biểu diễn môt máy bất định cụ thể tương ứng vớimẫu mô tả bởi biểu thức chính quy đang xét.

2.4 Mô phỏng máy

Bước cuối cùng trong việc phát triển một thuật toán đối sánh mẫu biểu thức chínhquy là viết một chương trình mà nó mô phỏng theo một cách nào đó thao tác của máyđối sánh mẫu bất định. Ý tưởng để viết một chương trình mà nó có thể "đoán ra" câutrả lời đúng là có vẻ khôi hài. Tuy nhiên trong trường hợp này nó dẫn tới một điều làchúng ta có thể lưu giữ dấu vết của tất cả các máy có thể có theo một phương phápcó hệ thống, sao cho ta thực sự tìm ra được đúng cái cần tìm.

Có khả năng xây dựng một chương trình đệ quy bắt chước một máy bất định(nhưng thử tất cả các khả năng thay vì đoán ra đúng). Thay vì dùng cách tiếp cậnnày, ta sẽ xem xét một cài đặt không đệ quy biểu diễn các nguyên lý thao tác cơ bảncủa phương pháp bằng cách lưu các trạng thái qua việc xem xét một cấu trúc dữ liệuđặc biệt gọi là deque.

Ý tưởng là lưu giữ dấu vết của tất cả các trạng thái mà nó có thể bị bắt gặp trongkhi máy đang "nhìn vào" ký tự nhập hiện thời. Mỗi một trạng thái được xử lý lầnlượt: các trạng thái NULL dẫn tới hai trạng thái (hay ít hơn), các trạng thái cho kýtự mà nó không khớp với ký tự nhập vào hiện thời thì được loại ra, và các trạng tháicho các ký tự mà nó khớp với ký tự nhập vào hiện thời sẽ dẫn tới việc dùng các trạngthái mới khi máy đang nhìn vào ký tự kế tiếp. Vì vậy, ta muốn duy trì danh sách củatất cả các trạng thái mà máy bất định có thể có ở một điểm cụ thể trong văn bản. Vấnđề là thiết kế một cấu trúc dữ liệu thích hợp cho danh sách này.

Việc xử lý các trạng thái NULL có vẻ cần một ngăn xếp (stack), vì chủ yếu ta

Page 30: Cac Giai Thuat Ve Xau Ky Tu

2.4.Mô phỏng máy 27

đang trì hoãn việc thực hiện một trong hai điều, như trong việc khử đệ quy (như vậytrạng thái mới sẽ được đặt và nơi bắt đầu của dánh sách hiện hành, chỉ e rằng nó bịtrì hoãn vô hạn). Việc xử lý các trạng thái khác có vẻ cần đến một hàng đợi (queue),vì ta không muốn xác định các trạng thái cho ký tự nhập kế tiếp cho nên khi đã xongký tự hiện hành (như vậy trạng thái mới sẽ được đặt vào cuối danh sách hiện tại).Thay vì chọn một trong hai cấu trúc dữ liệu này, chúng ta sẽ dùng cả hai! Deques(các hàng hai đầu: double-ended queues) là tổ hợp đặc trưng của ngăn xếp và hàngđợi: một deque là một danh sách các phần tử có thể thêm được vào cả hai đầu.

Một tính chất quyêt định của mày là nó không có các vòng lặp mà chỉ gồm cáctrạng thái NULL, vì nếu không nó có thể quyết định một cách không xác định làlặp vĩnh viễn. Từ đó dẫn đến một điều là số trạng thái trên hàng đợi ở bất kỳ thờiđiểm nào là bé hơn số ký tự trong mẫu mô tả.

Chương trình dưới đây dùng một deque để mô phỏng các hoạt động của một máyđối sánh mẫu bất định như đã mô tả ở trên. Khi đang kiểm tra một ký tự cụ thể trongdãy nhập, máy bất định có thể ở bất kỳ trạng thái nào trong số những trạng thái sauđây: chương trình lưu giữ dấu vết của những ký tự này trong một deque, bằng cáchdùng thủ tục push, put, và pop (các bạn tự tìm hiểu thêm phần cài đặt cụ thể).

Vòng lặp chính trong chương trình loại bỏ một trạng thái từ deque và thực hiệnhành động yêu cầu. Nếu một ký tự sắp được đối sánh, thì ký tự nhập sẽ được kiểm traxem có phải là ký tự được yêu cầu không; nếu đúng, thì sự chuyển tiếp trạng thái sẽđược tác động bởi việc đặt trạng thái mới ở cuối deque (sao cho tất cả các trạng tháicó liên quan đến ký tự hiện thời sẽ được xử lý trong những trạng thái có liên quanđến ký tự kế tiếp). Nếu trạng thái là NULL, hai trạng thái có thể mà nó sẽ đượcmô phỏng được đặt ở nơi bắt đầu của deque. Các trạng thái có liên quan đến ký tựnhập hiện tại được lưu riêng khỏi các ký tự có liên quan đến văn bản bằng một dấuhiệu scan = −1 trong deque; khi scan bị bắt gặp, con trỏ trỏ vào trong chuỗi nhậpsẽ được đẩy tới trước. Vòng lặp kết thúc khi tới cuối dáy nhập (không tìm thấy mộtsự trùng khớp nào), hoặc tới trạng thái 0 (đã tìm thấy sự trùng khớp hợp lệ), hoặc chimột phần tử, dấu hiệu scan, là còn lại trong deque (không tìm được sự trùng khớp).Ta có cái đặt như dưới đây.

Hàm này lấy tham số vị trí j trong chuỗi văn bản a ở đó nó sẽ bắt đầu thử đốisánh. Nó trả về chỉ số của ký tự cuối cùng khi đã tìm thấy được sự trùng khớp (nếucó), nếu không trả về j − 1.

Page 31: Cac Giai Thuat Ve Xau Ky Tu

2.4.Mô phỏng máy 28

Algorithm 8 Thuật toán mô phỏng máyfunction: match(j : integer) : integer;1: const scan = −1;2: var state, n1, n2 : integer;3: begin4: dequeinit; put(scan);5: match = j − 1; state = next1[0];6: repeat7: if state = scan then8: j := j + 1; put(scan);9: else if character[state] = a[j] then10: put(next1[state]);11: else if character[state] =′′ then12: n1 := next1[state]; n2 := next2[state];13: push(n1);14: if n1 <> n2 then15: push(n2);16: end if17: state := pop;18: end if19: until (j > N) or (state = 0) or (dequeempty)20: if state = 0 then21: match := j − 1;22: end if23: end;

Thời gian chạy của chương trình này hiển nhiên phụ thuộc rất nhiều vào mẫu đangđược so khớp. Tuy nhiên, đối với mỗi ký tự trong số N ký tự nhập vào, nó xử lý hầuhết M trạng thái của máy, như thế thời giàn thực hiện trong trường hợp xấu nhất sẽtỉ lệ vơi M × N cho mỗi vị trí bắt đầu trong văn bản.

Tính chất 2.4.1Sự mô phỏng thao tác của một máy M trạng thái để tìm các mẫu trong chuỗi vănbản gồm N ký tự sẽ mất M × N chuyển tiếp trong trường hợp xấu nhất.

Không phải tất cả các máy bất định đểu có thể được mô phỏng một cách hiệu quảnhư vậy, nhưng việc dùng một cái máy đối sánh mẫu giả thiết đơn giản trong ứngdụng này dẫn đến một thuật toán hoàn toàn hợp lý cho một bài toán thật khó. Tuynhiên để hoàn tất giải thuật, ta cần một chương trình dịch các biểu thức chính quytùy ý thành các máy mô phỏng dùng trong việc thông dịch bởi chương trình ở trên.Hy vọng rằng trong thời gian tới tôi sẽ phát triển tiểu luận này để nghiên cứu cụ thểmột chương trình như vậy trong ngữ cảnh một bạn luận khái quát hơn về các trìnhbiên dịch và các kỹ thuật phân tich cú pháp.

Page 32: Cac Giai Thuat Ve Xau Ky Tu

Tài liệu tham khảo

[1] Robert Sedgewirk, Dịch giả: Trần Đan Thư, Vũ Mạnh Tường, Dương Vũ DiệuTrà, Nguyễn Tiến Huy, Cẩm nang thuật toán, Tập 1, Các thuật toán thôngdụng, NXB Khoa học và Kỹ thuật, 1 - 2004.

[2] Robert Sedgewirk, Algorithms, Addison-Wesley Publishing Company, 8 -1984.

[3] Các webside về thuật toán trên INTERNET., tiêu biểu như:http://www.cs.utexas.edu/users/moore/best-ideas/string-searching/index.html,http://www.dcc.uchile.cl/%7Erbaeza/handbook/algs/7/712.srch.p.html,http://www.inf.fh-flensburg.de/lang/algorithmen/pattern/index.htm,http://www-igm.univ-mlv.fr/∼lecroq/string/examples/exp14.html,http://www.personal.kent.edu/%7Ermuhamma/Algorithms/algorithm.html,http://www.cs.utexas.edu/%7Emoore/best-ideas/string-searching/index.html,http://www-igm.univ-mlv.fr/%7Elecroq/string/node20.html,http://crypto.cs.mcgill.ca:80/∼crepeau/CS250/2004/33.Strings.pdf,http://www.inf.fh-flensburg.de/lang/indexen.htm.