7. Ý nghĩa khoa học của đề tài
2.1.3. Chuyển đổi Burrows-Wheeler nghịch
Chuyển đổi nghịch là lấy văn bản đã được hoán vị BWT và khôi phục đầu vào gốc T. Chuyển đổi nghịch có phần khó để cài đặt hơn chuyển đổi thuận , nhưng nó vẫn có thể được thực hiện trong thời gian và không gian O(n). Thông thường hai mảng chỉ số O(n) sẽ cần thiết, cộng với hai mảng O(|Σ|) để đếm các ký tự trong đầu vào. Có nhiều cách để thực hiện việc giải mã.
Có một thuật toán đơn giản như sau:
Bước 1: Lấy các kí tự trong xâu cuối cùng, sắp xếp lại theo thứ tự từ điển Bước 2: (Lặp)
- Lấy các kí tự ở xâu cuối cùng, thêm vào các xâu đã có. - Sắp xếp lại theo thứ tự từ điển
Ví dụ: Ta cần giải mã xâu (NNBAAA,4 )
N A AB ABA ABAN ABANA ABANAN
N A AN ANA ANAB ANABA ANABAN
B A AN ANA ANAN ANANA ANANAB
A B BA BAN BANA BANAN BANANA(4)
A N NA NAB NANA NABAN NABANA
A N NA NAN NANA NANAB NANABA
Hình 2.3: Minh họa việc giải mã Burrows Wheeler
Như vậy, ta đã có xâu BANANA ở vị trí 4. Dễ thấy, ta cần thêm vào n lần (n là độ dài của xâu) vì thế cần n lần sắp xếp. Nếu ta dùng thuật toán sắp xếp hiệu quả như QuickSort ta cần NlogN; không những thế, việc so sánh xâu cũng mất khá nhiều thời gian. Nhìn chung, thuật toán này có độ phức tạp N.LogN. Độ phức tạp này không thể chấp nhận được trong thực tế khi mà các file văn bản chứa cả triệu kí tự.
Nhận xét rằng: Kí tự thứ K theo sắp xếp từ điển xâu X sẽ là kí tự đầu tiên cuả xâu gốc. Như vậy, ta đã biết kí tự đầu tiên của xâu gốc. Nếu bây giờ bỏ kí từ đầu tiên này đi, ta phải tìm xem xâu N-1 kí tự còn lại nằm ở vị trí nào? Hãy xem kí tj đầu tiên này nằm ở vị trí thứ mấy trong xâu X mà chưa được đánh dấu. Vậy xâu N-1 kí tự còn lại cũng nằm ở vị trí đó. Tiếp tục lặp lại việc tìm kiếm này ta sẽ được xâu gốc.
Ví dụ: X = NNBAAA, K = 4, xâu gốc là xâu S
Bước 1: Sắp xếp lại các kí tự của xâu X theo thứ tự từ điển. Ta có xâu Xsx= AAABNN. Đánh dấu vị trí K. Bước 2: K = 4, kí tự thứ 4 trong xâu Xsx là B. Vậy S1 = B
Bước 3: Kí tự B xuất hiện lần đầu tiên chưa bị lấy trong X là vị trí thứ 3. Gán K = 3.
Bước 4: K = 3, kí tự thứ 3 trong xâu Xsx là A. Vậy S2 = A
Bước 5: Kí tự A xuất hiện lần đầu tiên chưa bị lấy trong X là vị trí thứ 5. Gán K = 5.
Bước 6: K = 5, kí tự thứ 5 trong xâu Xsx là N. Vậy S3 = N
Bước 7: Kí tự N xuất hiện lần đầu tiên chưa bị lấy trong X là vị trí thứ 1. Gán K = 1.
Bước 8: K = 1, kí tự thứ 1 trong xâu Xsx là A. Vậy S4 = A
Bước 9: Kí tự A xuất hiện lần đầu tiên chưa bị lấy trong X là vị trí thứ 6. Gán K = 6.
Bước 10: K = 6, kí tự thứ 6 trong xâu Xsx là N. Vậy S5 = N
Bước 11: Kí tự N xuất hiện lần đầu tiên chưa bị lấy trong X là vị trí thứ 2. Gán K = 2.
Bước 12: K = 2, kí tự thứ 2 trong xâu Xsx là A. Vậy S5 = A Vậy kết quả xâu S là: BANANA
Thuật toán trên có thể tóm tắt như sau: S:= ;
S:= S + kitu(K);
For i:=2 to Length(X) do Begin
S:=S + kitu (K); End;
Ta thấy, các thao tác cơ bản gồm 2 thao tác là tìm vị trí chưa bị lấy trong X của kí tự c, và tìm thứ tự thứ K trong xâu sắp xếp SSX.
Một cách dễ dàng theo các mối quan hệ trên là số lượng xuất hiện của các ký tự trong F và L. Hình 2.4 cho thấy các cột F và L từ Hình 2.3, nhưng phải đánh số các xuất hiện mỗi ký tự theo thứ tự từ đầu đến cuối bằng cách sử dụng các chỉ số dưới. Điều này làm cho xâu được giải mã dễ biểu thị. Ví dụ, dòng thứ ba có L[C = B1, và tương ứng F[3] cho biết nó được theo sau bởi A3. Vì A3 bằng L[6], ký tự tiếp theo có được từ F[6] là N2. Vì N2 bằng L[2], ký tự tiếp theo có được từ F[2] là A2. Vì A2 bằng L[5], ký tự tiếp theo có được từ F[5] là N1. Vì N1 bằng L[1], ký tự tiếp theo có được từ F[1] là A1.
Toàn bộ xâu được giải nén theo thứ tự B1 A3 N2 A2 N1 A1 `
F L 1 A1 N1 2 A2 N2 3 A3 B1 4 B1 A1 5 N1 A2 6 N2 A3
Hình 2.4 Sử dụng thứ tự ký tự để thực hiện chuyển đổi ngược
Trong thực tế bộ giải mã không khôi phục As hoặc F đầy đủ , nhưng mặc nhiên tạo ra các chỉ mục để biểu diễn đủ cấu trúc của nó để giải mã xâu gốc . L được lưu trữ một cách rõ ràng (bộ giải mã chỉ đọc đầu vào và lưu nó trong L), nhưng F mặc nhiên được lưu trữ để tiết kiệm không gian và cung cấp hiệu quả kiểu thông tin cần thiết trong suốt quá trình giải mã.
Hình 2.5 cho thấy ba mảng phụ trợ rất hữu ích cho việc giải mã. K[c] để đếm bao nhiêu lần mỗi ký tự c xuất hiện trong F, M[c] định vị vị trí đầu tiên của ký tự c
trong mảng F, vì vậy K cùng với M lưu trữ hiệu quả thông tin trong F. C[i] lưu trữ số lần ký tự L[i] xuất hiện trong L sớm hơn vị trí i. Ví dụ, ký tự cuối cùng trong L là i, và
i xuất hiện 3 lần trong thành phần sớm hơn của L. Ba mảng đó làm cho nó dễ nghiên cứu toàn bộ đầu vào theo cách ngược.
K M F L C A 3 A B A N A N 0 B 1 A N A B A N 1 N 2 A N A N A B 0 B A N A N A 0 N A B A N A 1 N A B A N A 2
Hình 2.5 Mảng (As) mặc nhiên được khôi phục để giải mã xâu NNBAAA
Thuật toán 2.1 cho thấy đầu vào (văn bản được chuyển đổi L và bắt đầu với chỉ số a) được sử dụng để xây dựng ba mảng đó như thế nào, sau đó được sử dụng để đưa ra văn bản được giải mã Q.
Thuật toán 2.1 BWT-Decode(L, a) 1 for c ← 1 to |Σ| do 2 K[c] ← 0 3 end for 4 5 for i ← 1 to n do 6 C[i] ← K[L[i]] 7 K[L[i]] ← K[L[i]] + 1 8 end for 9 10 sum ← 1 11 for c ← 1 to |Σ| do 12 M[c] ← sum 13 sum ← sum + K[c] 14 end for 15
16 i ← a
17 for j ← n downto 1 do
18 Q[j] ← L[i]
19 i ← C[i] +M[L[i]]
20 end for
Thuật toán 2.1: Phục hồi văn bản gốc theo chiều ngược
BWT theo cách ngược yêu cầu bốn mảng (L, K, C và M). K và M chỉ chứa |Σ| đầu vào (các ký tự được biểu diễn bằng các số nguyên từ 1 đến |Σ|). L và C chứa n giá trị, và do đó sử dụng không gian O(n). Thông thường Q sử dụng không gian O(n) để lưu trữ xâu ngược trước khi nó có thể được lưu trữ theo đúng thứ tự . Thời gian thực hiện thuật toán 2.1 là O(n) + O(|Σ|), vì công việc chí nh là trong hai bước chuyển thông qua n tập mục đầu vào – một để đếm và một để giải mã chúng .
Giá trị C[i] + M[L[i]] là chìa khóa để điều hướng thông qua L để giải mã xâu gốc, do đó thay vì thực hiện để giải mã ngay lập tức (các dòng 16 đến 20 của thuật toán 2.1), một mảng V được tạo ra để lưu trữ thông tin điều hướng , được thể hiện trong thuật toán 2.2. Mảng này có thể sau đó được sử dụng với bước ngược thông qua các ký tự gốc. Ký tự L[i] được đứng trước ký tự L[V[i]]. Các giá trị của V với ví dụ này được thể hiện trong Hình 2.6.
Thuật toán 2.2
Compute-array-V(C,M,L)
1 i ← a
2 for j ← n downto 1 do
3 V [i] ← C[i] +M[L[i]]
4 i ← V [i] 5 end for
Thuật toán 2.2: Tạo mảng V cho phép giải mã hiệu quả của đầu vào.
Thật là dễ để tạo ra một mảng phụ mà sẽ giải mã văn bản gốc theo chiều xuôi hơn là chiều ngược . Mảng này được gọi là W để xác định vị trí của ký tự trong L mà đến sau ký tự hiện hành, được so sánh với V, mà đưa vị trí đó đến trước. Mảng mới V
Hình 2.6. Các mảng phụ trợ V và W có thể được sử dụng để giải mã xâu mẫu
Thuật toán 2.3 cho thấy mảng W có thể được tạo ra như thế nào. Cũng giống như V, mảng W được tạo ra trong thời gian O(n).
Thuật toán 2.3 Compute-array-W(M, L) 1 for i ← 1 to n do 2 W[M[L[i]]] ← i 3 M[L[i]] ← M[L[i]] + 1 4 end for
Thuật toán 2.3: Tạo ra mảng W cho phép để giải mã đầu vào
W sau đó có thể được sử dụng để tạo ra văn bản gốc theo thứ tự đúng của nó bằng cách sử dụng dãy đơn được thể hiện trong thuật toán 2.4.
Thuật toán 2.4 Decode-with-array-W(W, L) 1 i ← a 2 for j ← 1 to n do 3 Q[j] ← L[i] 4 i ← W[i] 5 end for
Thuật toán 2.4: Giải mã văn bản gốc theo thứ tự đúng của nó bằng cách sử dụng W