Bài toán đối sánh chuỗi ký tự có thể được đặc trưng như một bài toán tìm kiếm, trong đó mẫu P được xem như khoá, tuy nhiên các thuật toán tìm kiếm thông thường không áp dụng được một các
Trang 1KHOA CÔNG NGHỆ THÔNG TIN
****** *****
BÁO CÁO BÀI TẬP LỚN MÔN HỌC PHÂN TÍCH VÀ ĐÁNH GIÁ THUẬT TOÁN
Đề bài : Bài toán đối sánh mẫu Thuật toán Boyer - Moore
Giáo viên hướng dẫn : PGS.TS Đào Thanh Tĩnh Học viên thực hiện : Bùi Thị Thao
Hà Nội, tháng 5/2014
MỤC LỤC PHẦN 1: KHÁI NIỆM CƠ BẢN VỀ ĐỐI SÁNH XÂU KÍ TỰ………… 3
1
Trang 21.1 Giới thiệu 3
1.3 Thuật toán đối sánh chuỗi kí tự đơn giản 4
1.3.1 Thuật toán 4
1.3.2 Mô tả thuật toán 4
1.3.3 Ví dụ mô phỏng thuật toán 4
1.3.4 Phân tích thuật toán 7
PHẦN 2: CÁC THUẬT TOÁN ĐỐI SÁNH XÂU KÍ TỰ ……… 8
2.1 Thuật toán Knuth – Morris – Pratt (KMP) 8
2.1.1 Ý tưởng thuật toán 8
2.1.2 Phát biểu thuật toán 8
2.1.3 Ví dụ mô phỏng thuật toán 9
2.1.4 Đánh giá độ phức tạp của thuật toán 12
2.2 Thuật toán Boyer – Moore (BM) 12
2.2.1 Ý tưởng thuật toán 12
2.2.2 Thuật toán BM 14
2.2.3 Ví dụ mô phỏng thuật toán 16
2.2.4 Đánh giá độ phức tạp của thuật toán 18
Trang 3PHẦN 1: KHÁI NIỆM CƠ BẢN VỀ ĐỐI SÁNH XÂU KÍ TỰ
1.1 Giới thiệu
Ngày nay, với sự phát triển của Công nghệ thông tin, máy tính đã thâm nhập vào tất cả các lĩnh vực của xã hội Việc số hóa dữ liệu, lưu trữ và xử lý các văn bản trên máy tính là một việc làm hết sức quan trọng
Trong các hệ xử lý văn bản chuỗi ký tự được coi là thành phần trung tâm, các hệ
xử lý văn bản cung cấp nhiều thuật toán đóng vai trò quan trọng trong việc thao tác trên các chuỗi ký tự Một phép toán cơ bản trên chuỗi ký tự là đối sánh chuỗi ký tự (String Matching) Cho trước một chuỗi văn bản T có độ dài n và một mẫu P có độ dài m, hãy tìm
sự xuất hiện của mẫu P trong văn bản T
Bài toán đối sánh chuỗi ký tự có thể được đặc trưng như một bài toán tìm kiếm, trong đó mẫu P được xem như khoá, tuy nhiên các thuật toán tìm kiếm thông thường không áp dụng được một cách trực tiếp vì mẫu P có thể dài và nó trải trên 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ị có nhiều thuật toán khác nhau như Brute-Force (BF), Knuth-Morris-Pratt (KMP), Boyer-Moore (BM), Karp- Rabin (KR), …
1.2 Bài toán
Giả sử chúng ta có một văn bản T là một mảng có độ dài là n (T[1 n]) và một chuỗi mẫu P có độ dài là m (P[1 m]) (điều kiện m<=n) Các phần tử của T và P là các ký
tự trong tập hữu hạn alphabet VD: = {0,9} hoặc = {a,b,c ,z,A,B,C,…,Z} Mảng} Mảng
ký tự P và T được gọi là chuỗi ký tự
Bài toán: P T ?
Nếu P T: Xác định vị trí xuất hiện đầu tiên của P trong T?
Khi đó bài toán đối sánh chuỗi được phát biểu như sau: Cho một chuỗi T ban
đầu và một chuỗi mẫu P Đối sánh chuỗi ký tự chính là việc tìm chuỗi mẫu P trong chuỗi
T ban đầu Nếu chuỗi mẫu P được tìm thấy trong chuỗi T ban đầu hãy chỉ ra vị trí trong T
mà tại đó chuỗi mẫu P được tìm thấy
Trang 41.3 Thuật toán đối sánh chuỗi kí tự đơn giản
1.3.1 Thuật toán
- Input: P[1 m], T[1 n];
- Output: i0 (if P ≡ T[i0 i0+m-1], i0 ≥1, ngược lại i0=0)
a) i=1; j= i; k=1;
b) while (j≤n) & (k≤m)
if (T(j) = P(k))
{ j++; k++; }
else
{ i++; j=i; k=1;}
c) if (k>m) i0 := i;
else i0 :=0;
1.3.2 Mô tả thuật toán
Thuật toán thực hiện như sau: Tiến hành so sánh phần tử thứ nhất của chuỗi mẫu P với phần tử thứ nhất của chuỗi ban đầu T Nếu hai phần tử này giống nhau, tiến hành so sánh tiếp phần tử thứ 2 Cứ như vậy cho đến khi tìm được chuỗi P trong T hoặc phát hiện
ra vị trí mà tại đó phần tử của T và P khác nhau
Trong trường hợp phát hiện ra vị trí mà tại đó phần tử của T và P khác nhau ta tiến hành dịch phải chuỗi P đi một phần tử so với chuỗi T Lặp lại thao tác so sánh như trên cho đến khi đạt được kết quả, hoặc không tìm thấy nếu duyệt hết chuỗi T
1.3.3 Ví dụ mô phỏng thuật toán
Giả sử cho chuỗi T = “ABCABCDABABCDA1DA1BDA”
Chuỗi P = “ABCDA1”
Tiến hành thực hiện thuật toán đối sánh mẫu đơn giản qua các bước như sau:
Trang 5 Bước 1:
i: 1
j: 1
P: A B C D A 1
k: 1
n = 21
m = 6
i=1, j=1, k=1 ta có T[1] = P[j] tăng j = j+1= 2 và k = k+1 = 2 để tiếp tục so sánh
Bước 2:
i: 1
j: 1 2
P: A B C D A 1
k: 1 2
i =1, j=2, k= 2 ta có T[2] = P[2] tăng j = j+1 =3 và k = k+ 1=3 để tiếp tục so sánh
Bước 3:
i: 1
j: 1 2 3
P: A B C D A 1
k: 1 2 3
i = 1, j= 3, k = 3 ta có T[3] = P[3] tăng j =j+1 =4 và k = k+ 1= 4 để tiếp tục so sánh
Bước 4:
i: 1
Trang 6j: 1 2 3 4
P: A B C D A 1
k: 1 2 3 4
i = 1, j = 4, k = 4 ta có T[4] <> P[4] tăng i = i+1 =2 (dịch chuỗi P sang phải 1
vị trí so với T để tiếp tục so sánh), gán j = i =2, k=1
Bước 5:
i: 1 2
j=2, k =1 ta có T[2] <> P[1] tăng i = i+1 =3 ( dịch chuỗi P sang phải 1 vị trí so với T để tiếp tục so sánh), gán j = i = 3, k =1 và bắt đầu so sánh
Cứ như vậy lặp đi lặp lại các bước của thuật toán đối sánh chuỗi Sau 9 lần dịch phải chuỗi P so với chuỗi T ta thu được kết quả như sau:
i: 1 2 3 4 5 6 7 8 9 10
j:
k:
Kết quả tìm được vị trí xuất hiện đầu tiên của P trong T là 10
1.3.4 Phân tích thuật toán
Việc tiến hành theo đúng các bước của thuật toán đối sánh chuỗi như trên sẽ trả cho ta kết quả xem có tìm thấy chuỗi mẫu P trong chuỗi T ban đầu hay không Nếu chuỗi mẫu P được tìm thấy thì tìm thấy ở vị trí phần tử nào trên chuỗi T
Tuy nhiên thuật toán đối sánh chuỗi này còn gặp nhược điểm lớn về thời gian thực hiện thuật toán như sau:
- Với n là độ dài của chuỗi T ban đầu, m là độ dài của chuỗi mẫu P thì thời gian so
khớp hai chuỗi là O(mn) Đây là một thời gian lớn làm cho thuật toán có tốc độ chậm.
Trang 7- Nguyên nhân của việc chậm này là do việc so sánh các phần tử của chuỗi T được thực hiện lặp đi lặp lại n lần trong những lần so sánh tiếp theo Và tại mỗi vị trí i trong T
ta so sánh m ký tự trong P Như vậy trong trường hợp xấu nhất sẽ có mn phép so sánh Như vậy độ phức tạp thuật toán là O(mn).
Trong ví dụ đã nêu ở trên việc so sánh hai phần tử ở vị trí tương ứng của hai chuỗi
T và P phát hiện ra vị trí khác nhau đầu tiên sau 4 lần so sánh, tức là T[4] và P[4] khác nhau Theo đúng thuật toán chuỗi mẫu P sẽ dịch sang phải so với chuỗi T một vị trí và việc so sánh lại bắt đầu lại từ đầu Mặc dù trong lần so sánh trước đã biết giá trị T[1] và biết T[1] khác P[1] Đây là việc làm lặp đi lặp lại đối với T[1] trong các lần so sánh tiếp theo, làm tăng thời gian thực hiện thuật toán
Như vậy chúng ta thấy thuật toán đối sánh chuỗi ký tự đơn giản không phải là một thuật toán tối ưu Vì vậy cần xem xét đến một phương án tối ưu hơn, có thời gian thực hiện thuật toán nhỏ hơn
Trang 8PHẦN 2: CÁC THUẬT TOÁN ĐỐI SÁNH XÂU KÍ TỰ 2.1 Thuật toán Knuth – Morris – Pratt (KMP)
2.1.1 Ý tưởng thuật toán
Tư tưởng cơ bản của thuật toán Knuth-Morris-Pratt (KMP) là thay vì ta so sánh mẫu với lần lượ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àm sao giảm được số các phép so sánh Cụ thể là khi phát hiện ra có sự không khớp giữa hai chuỗi, ta không gán i như trong thuật toán đối sánh chuỗi ở trên để 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 T, mà chúng ta tìm cách để dịch chuyển mẫu với số đơn vị lớn hơn hoặc bằng 1, và giảm số ký tự phải so sánh lại
Ví dụ: Nếu mẫu có dạng là một xâu nhị phân có dạng đặc biệt là BAAAAAAA (ký
tự đầu tiên của mẫu chỉ xuất hiện 1 lần) Khi đó giả sử có một khởi đầu sai dài i ký tự (tức
là i 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 T ) Như vậy nếu
ký tự thứ i+1 là không khớp thì ta biết rằng i ký tự trước trong mẫu đã khớp nhau rồi Điều đó có nghĩa là i ký tự này có dạng ở cả trong mẫu lẫn văn bản 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 P[1] với i-1 ký tự tiếp theo trong văn bản, hay nói cách khác dịch chuyển mẫu sang i đơn vị gán lại (gán lại j=1 còn i vẫn giữ nguyên) Để so sánh kí tự đầu tiên của mẫ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 ra trường hợp đặc biệt như vậy, và thuật toán KMP là sự tổng quát hóa từ tư tưởng đó
2.1.2 Phát biểu thuật toán
Trước hết chúng ta cần xây dựng một mảng tiền tố 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 của mẫu P với T
Chúng ta dịch chuyển mộ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ự các khớp nhau, hay không có ký tự nào khớp nhau cả Khoảng cách để dự phòng
trong mẫ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 Đặc biệt là 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 cho k-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 trong mẫu Mặt khác chúng ta cũng định nghĩa next[1] = 0 Ta có thuật toán tính mảng next như sau:
Thuật toán: Tính mảng tiền tố next
Trang 9Next [1…m]
Next[1] = 0;
For k=1 to m do
t=Next[k];
while (t>0) & (Pt<>Pk)
t = Next[t];
Next[k+1] = t+1;
Thuật toán: Tìm vị trí mẫu P trong T với KMP
Input: chuỗi mẫu P và chuỗi văn bản gốc T, next là mảng tiền tố lưu khoảng dự
phòng cho bước nhảy
Output: vị trí tìm thấy của sâu P trong chuỗi văn bản T, nếu vị trí đó >n thì không tìm
thấy
Input: P[1…m], T[1…n];
Output: i0 (if P T[i0……. i0+m-1] , i0 ≥ 1, ngược lại i0=0)
a) k:=1; j:=1;
b) while(j<=n) & (k<=m)
if (k=0) or (T[j]=P[k])
{ j++; k++; }
else
k=Next(k);
c) if(k>m) i0 :=j - m;
else i0 :=0;
2.1.3 Ví dụ mô phỏng thuật toán
Giả sử cho chuỗi văn bản T = “ABCABCDABCDA1ABDA1BA”
Chuỗi mẫu P = “ABCDA1”
Trước hết ta xây dựng mảng Next[1…m] cho mẫu P = “ABCDA1”
Trang 10K 1 2 3 4 5 6 7
Tiến hành thực hiện thuật toán KMP qua các bước như sau:
- Với m=6, n=20, k=1, j=1
T[1] = P[1], k=k+1, j=j+1
T: A B C A B C D A B C D A 1 A B D A 1 B A
j 1 2
k 1 2
P: A B C D A 1
T[2] = P[2], k=k+1, j=j+1
T: A B C A B C D A B C D A 1 A B D A 1 B A
j 1 2 3
P: A B C D A 1
T[3] = P[3], k=k+1, j=j+1
T: A B C A B C D A B C D A 1 A B D A 1 B A
j 1 2 3 4
k 1 2 3 4
P: A B C D A 1
T[4]≠P[4], k≠0: k=Next[k]=Next[4]=1
T: A B C A B C D A B C D A 1 A B D A 1 B A
j 1 2 3 4
Trang 11k 1
T[4] = P[1], k=k+1, j=j+1
T: A B C A B C D A B C D A 1 A B D A 1 B A
j 1 2 3 4 5
T[5] = P[2], i=i+1, j=j+1
T[6] = P[3], k=k+1, j=j+1
T[7] = P[4], k=k+1, j=j+1
T[8] = P[5], k=k+1, j=j+1
T: A B C A B C D A B C D A 1 A B D A 1 B A
j 1 2 3 4 5 6 7 8 9
T[9]≠P[6], k = Next[6] = 2
T: A B C A B C D A B C D A 1 A B D A 1 B A
j 1 2 3 4 5 6 7 8 9
T[9] = P[2], i=i+1, j=j+1
T[10] = P[3], k=k+1, j=j+1
T[11] = P[4], k=k+1, j=j+1
T[12] = P[5], k=k+1, j=j+1
T[13] = P[6], k=k+1, j=j+1
T: A B C A B C D A B C D A 1 A B D A 1 B A
j 1 2 3 4 5 6 7 8 9 10 11 12 13 14
Trang 12k 2 3 4 5 6 7
Với j=14, k=7> 6=m ta có: i0= j-m=14-6=8 Vậy vị trí xuất hiện đầu tiên của mẫu P trong T là 8
Tương tự ta xét tiếp xem P có xuất hiện ở vị trí nào khác trong T nữa không
T: A B C A B C D A B C D A 1 A B D A 1 B A
j 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Output: i0=8 (tức là chuỗi P xuất hiện 1 lần trong T tại vị trí 8)
2.1.4 Đánh giá độ phức tạp của thuật toán
Đối với thuật toán tính mảng next, vòng lặp thực hiện đúng m bước lặp, việc gán giá
trị trước đó chỉ thực hiện 1 lần Vì vậy thuật toán sinh mảng tiền tố Next[ ] sẽ có độ phức tạp là O(m)
Đối với thuật toán KMP: trong trường hợp xấu nhất, không tìm thấy mẫu P thì vòng lặp phải thực hiện đúng n bước lặp Thời gian chạy của vòng lặp tìm kiếm sẽ là: O(n)
Độ phức tạp của giải thuật KMP sẽ là O(m+n) tốt hơn so với giải thuật thông thường với độ phức tạp là O(m.n)
2.2 Thuật toán Boyer – Moore (BM)
2.2.1 Ý tưởng thuật toán
Thuật toán Boyer Moore là thuật toán tìm kiếm chuỗi rất có hiệu quả trong thực tiễn, các dạng khác nhau của thuật toán này thường được cài đặt trong các chương trình soạn thảo văn bản
Thuật toán Boyer-Moore kiểm tra các ký tự của mẫu từ phải sang trái và khi phát hiện sự khác nhau đầu tiên, thuật toán sẽ tiến hành bước nhảy Trong thuật toán này có hai trường hợp của bước nhảy
Trang 13Trường hợp 1: Gần giống như cách dịch trong thuật toán KMP, dịch sao cho những
phần đã so sánh trong lần trước khớp với những phần giống nó trong lần sau
Trong lần kiểm tra tại vị trí j, khi so sánh đến ký tự i trên mẫu thì phát hiện ra
sự khác nhau, lúc đó P[i+1…m] = T[i+j j+m-1] = u và
a = P[i]T[i+j-1] = b khi đó thuật toán sẽ tiến hành bước nhảy sao cho đoạn u
= T[i+j…j+m-1] giống với một đoạn mới trên mẫu (trong các bước nhảy ta
chọn bước nhảy nhỏ nhất)
Dịch chuyển sao cho u xuất hiện lại và c a
Nếu không có một đoạn nguyên vẹn của u xuất hiện lại trong P, ta sẽ chọn sao cho phần đôi dài nhất của u xuất hiện trở lại ở đầu mẫu
Dịch chuyển để một phần đôi của u xuất hiện lại trên x
Trường hợp 2: Coi ký tự đầu tiên không khớp trên văn bản là b=T[i+j-1] ta sẽ
dịch sao cho có một ký tự giống b trên xâu mẫu khớp vào vị trí đó (nếu
có nhiều vị trí xuất hiện b trên xâu mẫu ta chọn vị trí phải nhất)
c
a
P
T
P
u
a
T
u
P
a
T
b
u b
u b
u b
Trang 14Dịch chuyển để ký tự b ăn khớp với văn bản.
Nếu không có ký tự b nào xuất hiện trên mẫu ta sẽ dịch mẫu sao cho ký tự trái
nhất của mẫu vào vị trí ngay sau ký tự T[i+j-1]=b để đảm bảo sự ăn khớp
Dịch chuyển khi b không xuất hiện trong x
Trong hai cách dịch thuật toán sẽ chọn cách dịch có lợi nhất
Trong cài đặt ta dùng mảng bmGs để lưu cách dịch 1, mảng bmBc để lưu phép dịch thứ 2 (ký tự không khớp) Việc tính toán mảng bmBc thực sự không có gì nhiều để bàn Nhưng việc tính trước mảng bmGs khá phức tạp, ta không tính trực tiếp mảng này
mà tính gián tiếp thông qua mảng suff
Có suff[i]=max{k|x[i-k+1…i]=x[m-k+1…m]}
2.2.2 Thuật toán BM
PHẦN 1: Khởi tạo mảng bmBc
procedure initBmBc (P:String);
begin
m = length (P);
1 for i := 0 to 255 do bmBc[i] := m;
2 for i := 1 to m - 1 do bmBc[Ord(P[i])] := P.length - i;
end;
procedure suffixes (P:String);
var
right, left, i: integer;
begin
m = length (P);
1 suff[m] := m;
a
T
u b
Trang 15left := m;
2 for i := m - 1 downto 1 do
if (i > left) and (suff[i + m - right] < i - left) then
suff[i] := suff[i +m - right]
else
a if (i < left) then left := i;
b right := i;
c while (left >= 1) and (X[left] = X[left + m - right]) do
left := left-1;
d suff[i] := right - left;
end;
PHẦN 2: Khởi tạo mảng bmGs
procedure initBmGs(P:String);
begin
m = length (P);
1 suffixes(P); {Tính mảng suff}
2 for i := 1 to m do
bmGs[i] :=m;
3 j := 0;
4 for i := m downto 0 do
if (i = 0) or (suff[i] = i) then
while (j < m - i) do
{Nếu bmGs[j] chưa có giá trị thì điền vào}
bmGs[j] := m - i;
j := j+1 ;
end;
5 for i := 1 to m - 1 do
Trang 16PHẦN 3: Thuật toán BM
procedure BM(P,T:String);
var
i, j: integer;
bmBc, bmGs: IndexArray;
begin
1 initBmBc(P);
2 while (j <= T.length – P.length + 1) do
2.1 i := P.length;
2.2 while (i >= 1) and (P[i] = T[i + j - 1]) do i:=i-1;
2.3 if (i < 1) then
a Output(j);
b j := j + bmGs[1];
else {chọn cách dịch được lợi nhất }
j := j + Max(bmGs[i], bmBc[Ord(T[i + j - 1])] - m + i);
end;
2.2.3 Ví dụ mô phỏng thuật toán
Cho 2 chuỗi ký tự
T = “HCATCHCAHAHAHTATACAHTACH”
P = “HCAHAHAH”
Khởi tạo mảng:
bmBC[c] 1 6 2 8
Trang 17i 1 2 3 4 5 6 7 8
suff[i] 1 0 0 2 0 4 0 8 bmGs[i] 7 7 7 2 7 4 7 1
Mô tả thuật toán
T H C A T C H C A H A H A H T A T A C A H T A C H
1
P H C A H A H A H
Bước nhảy = 1 (bmGs[7]=bmBc[A]-8+8)
T H C A T C H C A H A H A H T A T A C A H T A C H
3 2 1
P H C A H A H A H
Bước nhảy = 4 (bmGs[5]=bmBc[C]-8+6) Bước 3
T H C A T C H C A H A H A H T A T A C A H T A C H
8 7 6 5 4 3 2 1
Bước nhảy = 7 (bmGs[0])
T H C A T C H C A H A H A H T A T A C A H T A C H