Mục đích của mã entropy là nén dữ liệu khơng làm mất thơng tin, mã nén này cũng đƣợc dùng rất nhiều trong việc truyền thơng tin. Mã Huffman và mã số học là hai phƣơng pháp mã entropy đƣợc sử dụng rộng rãi nhất hiện nay.
1.2.1 Mã hĩa Huffman
Mã hĩa Huffman đƣợc coi là phƣơng pháp nén tốt, sử dụng các ký hiệu trong văn bản gốc để nén dữ liệu. Trong bất cứ văn bản nào đều cĩ các ký tự cĩ số lần
xuất hiện nhiều hơn so với các ký tự khác. Ví dụ nhƣ trong tiếng Anh, các chữ cái E, A, O, T thƣờng xuyên xuất hiện nhiều hơn các chữ cái J, Q, X.
Xét các chữ cái cĩ độ dài từ mã là 8-bit theo bảng mã ASCII. Ý tƣởng của thuật tốn Huffman là xét các chữ cái cĩ tần số xuất hiện nhiều hơn sẽ cĩ độ dài từ mã ngắn hơn, các chữ cái xuất hiện ít hơn sẽ cĩ độ dài từ mã dài hơn. Do đĩ tổng số bit của dữ liệu nén đƣợc sẽ giảm đáng kể so với dữ liệu nguồn.
Để rõ hơn ta xét chuỗi ký tự “BWTMTFHUFFMAN”. Ta cĩ bảng tần số xuất hiện của các ký tự:
B W T M F H U A N 1 1 2 2 3 1 1 1 1
Sau đĩ sắp xếp các ký tự theo tần số xuất hiện B W H U A N T M F 1 1 1 1 1 1 2 2 3
Ta cĩ bảng ký tự của dữ liệu nguồn là (B, W, H, U, A, N, T, M, F) với số lần xuất hiện tƣơng ứng là (1, 1, 1, 1, 1, 1, 2, 2, 3). Khi đĩ ta xây dựng bảng ký tự cĩ độ dài mã thay đổi.
Dữ liệu vào của thuật tốn Huffman là chuỗi ký tự. Dữ liệu ra là chuỗi các bit nhị phân tƣơng ứng với chuỗi ký tự vào. Để giải quyết vấn đề này ta xét ba vấn đề nhỏ sau:
1. Đọc dữ liệu vào 2. Mã hĩa mỗi ký tự vào
3. Kết xuất từ mã cho mỗi ký tự vào (nén).
Để giải quyết vấn đề 1 ta cần tổ chức dữ liệu vào sao cho cĩ thể đọc đƣợc từng ký tự ở chuỗi dữ liệu vào (định dạng tệp tin dƣới dạng byte) mỗi m u tin đọc vào là một byte cĩ mã tƣơng ứng trong bảng mã ASCII.
Đối với vấn đề 2 ta cần xây dựng cây nhị phân theo quy tắc sau: - Sắp xếp danh sách L các ký hiệu tăng dần theo tần số xuất hiện
- Kết hợp hai cây con cĩ gốc là giá trị tần số xuất hiện của hai cây con này, sau đĩ bổ sung gốc vào danh sách L theo thứ tự tăng dần của tần số xuất hiện. Thực hiện bƣớc này cho đến khi chỉ cịn một gốc.
Để giải quyết vấn đề 3 ta cần tìm từ mã tƣơng ứng với mỗi ký tự và kết xuất từ mã. Để tìm một từ mã tƣơng ứng với ký hiệu ta duyệt cây nhị phân từ nút gốc tới nút lá. Trong quá trình duyệt nếu đi sang cây con trái ta cĩ bit 0, nếu đi sang cây con phải ta cĩ bit 1, đƣờng đi từ nút gốc tới nút lá là độ dài từ mã.
Ta cĩ thể minh họa theo các bƣớc sau:
B W H U A N T M F 1 1 1 1 1 1 2 2 3 1) H U 1 1 A N 1 1 B W 1 1 2 T M F 2 2 3 2) T M F 2 2 3 A N 1 1 B W 1 1 2 H U 1 1 2 3) T M F 2 2 3 B W 1 1 2 H U 1 1 2 A N 1 1 2 4) T M F 2 2 3 B W 1 1 2 H U 1 1 2 A N 1 1 2 4 5)
Hình 1.11: Minh họa n n theo phương pháp Huffman T M F 2 2 3 B W 1 1 2 H U 1 1 2 A N 1 1 2 4 4 8) 5 8 T M F 2 2 3 B W 1 1 2 H U 1 1 2 A N 1 1 2 4 4 9) 5 8 13 T M F 2 2 3 B W 1 1 2 H U 1 1 2 A N 1 1 2 4 4 6) T M F 2 2 3 B W 1 1 2 H U 1 1 2 A N 1 1 2 4 4 7) 5
Trên mỗi nhánh của cây nhị phân ta gán các trị 0 hoặc 1 vào cây. Giá trị 0 gán vào nhánh bên trái, giá trị 1 gán vào nhánh bên phải. Cuối cùng ta cĩ cây nhị phân nhƣ sau – cịn đƣợc gọi là cây Huffman:
Hình 1.12: Cây Huffman
Sau khi thiết lập cây Huffman ta cĩ bộ mã tối ƣu tƣơng ứng với từng ký tự nhƣ sau:
B W H U A N T M F
1000 1001 1110 1111 1100 1101 101 00 01
Ta nhận thấy các ký tự cĩ tần số xuất hiện nhiều hơn thì sẽ cĩ độ dài mã ngắn hơn, đối với ký tự „M‟ và „F‟ xuất hiện nhiều hơn cĩ độ dài mã là 2, đối với ký tự „B‟, „W‟, „H‟, „U‟, „A‟, „N‟ cĩ số lần xuất hiện ít hơn sẽ cĩ độ dài mã là 4.
Tuy nhiên, để cĩ thể giải mã đƣợc dữ liệu đã nén, ta phải lƣu trữ bảng ký hiệu cùng với tần suất xuất hiện các ký hiệu vào tệp tin nén.
1.2.1.1 Quá trình mã hĩa
Thuật tốn nén theo Huffman đƣợc tiến hành từng bƣớc nhƣ sau: 1) Đọc từng ký tự ở tệp nguồn.
2) Tìm ký tự đĩ ở nút lá của cây nhị phân
3) Duyệt từ nút lá đến nút gốc, trên đƣờng đi thu nhận các bit 0 hoặc 1, ta đƣợc chuỗi bit của từng ký tự.
T 2 0 1 0 1 M F 2 3 B W 1 1 2 H U 1 1 2 A N 1 1 2 4 4 0 5 8 13 0 0 0 0 0 1 1 1 1 1 1
4) Đảo lại vị trí các bit của chuỗi ký tự thu đƣợc và bổ sung vào vùng đệm (nếu số bit ở vùng đệm lớn hơn hoặc bằng 8 bit ta ghi ký tự thu đƣợc vào tệp đích) 5) Lặp lại các bƣớc trên cho đến khi hết ký tự ở tệp nguồn.
Ví dụ đối với dãy ký tự “BWTMTFHUFFMAN” sau khi nén ta đƣợc chuỗi bit sau: 10001001 10100101 01111011 11010100 11001101
Nhƣ vậy thay vì lƣu trữ 13-byte ASCII hết 104 bit thì với thuật tốn Huffman ta lƣu trữ với 5 byte (40 bit).
1.2.1.2 Quá trình giải mã
Thuật tốn giải nén đƣợc thực thi nhƣ sau: 1) Đọc từng ký tự ở tệp nguồn
2) Duyệt từ gốc của cây tới nút lá căn cứ vào giá trị bit đọc đƣợc từ ký tự (nếu bit cĩ trị 1 ta duyệt tiếp con bên phải, nếu bit cĩ trị 0 ta duyệt tiếp con bên trái)
3) Ghi giá trị nút lá vào tệp đích
4) Lặp lại các bƣớc trên cho tới khi hết tệp nguồn. Đối với ví dụ trên, với chuỗi bit vào là:
10001001 10100101 01111011 11010100 11001101
Ta lần lƣợt đọc từng bit và duyệt từ gốc của cây Huffman tới nút lá nhƣ sau: - đọc bit 1 10001001 10100101 01111011 11010100 11001101 - đi tới nút của cây nhị phân
- đọc tiếp bit 0 10001001 10100101 01111011 11010100 11001101 - đi tới nút của cây nhị phân
- đọc tiếp bit 0 10001001 10100101 01111011 11010100 11001101 - đi tới nút của cây nhị phân
- đọc tiếp bit 0 10001001 10100101 01111011 11010100 11001101 - đi tới nút B là nút lá của cây nhị phân, ghi giá trị B vào tệp đích, quay trở lại gốc và đọc bit tiếp theo. Quá trình cứ tiếp tục cho đến khi đọc hết các bit trong tệp nguồn.
1.2. Mã hĩa s h c
Nén số học (ARC - Arithmetic coding) là một trong những kỹ thuật nén mạnh, dựa trên việc thống kê xác suất xuất hiện từng thành phần trong nội dung. Dựa vào tần xuất xuất hiện để chuyển dữ liệu nén sang dạng số thực chính xác đơn.
1.2.2.1 Mơ hình mã hĩa số học
Mơ hình mã hĩa và giải mã đƣợc biểu diễn nhƣ sau:
Hình 1.13: Mơ hình n n theo mã số học Trong đĩ: sk là ký tự đầu vào ck = k i i p 1
với pi là xác suất của ký tự si < sk trong dãy các ký tự đầu
vào theo bảng chữ cái.
sk là ký tự giải mã thu đƣợc
1.2.2.2 Quá trình mã hĩa
Hình 1.14: Mơ hình quá trình nén
Trong đĩ:
sk là ký hiệu cần mã hĩa p(sk) là sác xuất của ký hiệu sk
c(sk) là khoảng tích lũy sác xuất của các ký tự xuất hiện từ ký tự đầu tiên đến sk khoảng [bk, bk + lk) là giá trị mà sk đƣợc mã hĩa
trong đĩ, bk & lk đƣợc xác định theo cơng thức đệ quy sau: b0= 0
l0=1
bk = bk-1 + c(sk)lk-1 lk= p(sk)lk-1
Ví dụ : Cho dữ liệu cĩ tập A= { 0, 1, 2, 3}, tập xác suất p={0.1, 0.5, 0.2,0.2} Hãy mã hĩa tập S={3, 1, 1, 2, 2, 1
Từ đây ta cĩ tập C ={0, 0.1, 0.6, 0.8, 1 Quá trình mã hĩa đƣợc thể hiện qua bảng sau:
Bảng 1.5: Quá trình xác định [bk, lk) trong mã hĩa số học
k sk bk lk
0 3 0.8 0.2
2 1 0.83 0.05
3 2 0.86 0.01
4 2 0.866 0.002
5 1 0.8662 0.001
Kết quả là giá trị trong khoảng [0.8662 , 0.8672) Quá trình mã hĩa đƣợc thể hiện bằng mơ hình sau:
Hình 1.15: Mơ tả quá trình n n theo bảng 1.5
Qua hình, giá trị thu đƣợc nằm trong khoảng [0.8662, 0.8672)
1.2.2.3 Quá trình giải mã
Là quá trình xác định lại khoảng [bk, bk + lk) nhƣ quá trình nén. Tuy nhiên, với mỗi khoảng [bk, bk + lk) của từng ký tự đầu vào, ta tính đƣợc giá trị nén thuộc khoảng nào, để từ đĩ ta xác định đƣợc ký tự nào đƣợc tham gia trong quá trình nén. Ví dụ: với giá trị nén thu đƣợc từ ví dụ trên là a=0.8667 và với chiều dài bằng 6 thì quá trình giải nén đƣợc thực hiện nhƣ sau:
(Kết quả sau cĩ đƣợc từ code chƣơng trình) Lần 1
j= 0 : [0.00000000, 0.10000000) voi l=0.1000 j= 1 : [0.10000000, 0.60000000) voi l=0.5000 j= 2 : [0.60000002, 0.80000003) voi l=0.2000 j= 3 : [0.80000001, 1.00000001) voi l=0.2000
Chon x = 3 thuoc [0.80000001, 1.00000001) voi l=0.2000 Lần 2
j= 0 : [0.80000001, 0.82000001) voi l=0.0200 j= 1 : [0.81999999, 0.91999999) voi l=0.1000 j= 2 : [0.92000002, 0.96000002) voi l=0.0400 j= 3 : [0.96000004, 1.00000004) voi l=0.0400
Chon x = 1 thuoc [0.81999999, 0.91999999) voi l=0.1000 Lần 3
j= 0 : [0.81999999, 0.82999999) voi l=0.0100 j= 1 : [0.82999998, 0.87999998) voi l=0.0500 j= 2 : [0.88000000, 0.90000000) voi l=0.0200 j= 3 : [0.89999998, 0.91999998) voi l=0.0200
Chon x = 1 thuoc [0.82999998, 0.87999998) voi l=0.0500 Lần 4
j= 0 : [0.82999998, 0.83499998) voi l=0.0050 j= 1 : [0.83499998, 0.85999998) voi l=0.0250 j= 2 : [0.86000001, 0.87000002) voi l=0.0100 j= 3 : [0.87000000, 0.88000001) voi l=0.0100
Chon x = 2 thuoc [0.86000001, 0.87000002) voi l=0.0100 Lần 5
j= 0 : [0.86000001, 0.86100001) voi l=0.0010 j= 1 : [0.86100000, 0.86600000) voi l=0.0050 j= 2 : [0.86600000, 0.86800000) voi l=0.0020 j= 3 : [0.86800003, 0.87000003) voi l=0.0020
Lần 6
j= 0 : [0.86600000, 0.86620000) voi l=0.0002 j= 1 : [0.86619997, 0.86719997) voi l=0.0010 j= 2 : [0.86720002, 0.86760002) voi l=0.0004 j= 3 : [0.86760002, 0.86800002) voi l=0.0004
Chon x = 1 thuoc [0.86619997, 0.86719997) voi l=0.0010 Vậy kết quả là : 311221
CHƢƠNG II: NÉN KẾT HỢP VỚI KỸ THUẬT BIẾN ĐỔI DỮ LIỆU 2.1 Các kỹ thuật biến đổi dữ liệu cơ bản
Kỹ thuật biến đổi Burrow-Wheeler
Thuật tốn Burrows-Wheeler đã nhận đƣợc sự chú ý đáng kể trong những năm gần đây với cả hai lý do là đơn giản và hiệu quả. Thuật tốn dựa trên một hốn vị của dãy đầu vào. Biến đổi Burrows-Wheeler (BWT) – nhĩm các ký tự với một ngữ cảnh tƣơng tự lại với nhau. Hốn vị này đƣợc theo sau bằng chuyển đổi Move- To-Front (MTF) và giai đoạn mã hĩa entropy cuối cùng.
Quá trình biến đổi thuận và nghịch sẽ đƣợc giải thích thơng qua ví dụ của xâu đầu vào “LAHABANA”.
2.1.1.1 Biến đổi BWT thuận
Input: LAHABANA
1. Tạo ra tất cả các thay đổi quay vịng của chuỗi dữ liệu và liệt kê chúng theo thứ tự; sau mỗi lần thay đổi, phần tử đầu tiên của dãy cũ sẽ đƣợc chuyển về vị trí cuối cùng của dãy mới.
Shift:
Hình 2.1: Quá trình quay chuỗi “LAHABANA”
2. Sắp xếp các chuỗi theo thứ tự từ điển. (Sử dụng bubblesort, quicksort, suffix array sort, suffix tree sort, …)
Sort:
Hình 2.2: Kết quả sắp xếp theo th tự từ điển
Đầu ra của BWT là cột cuối cùng của khối và đƣợc lƣu trữ bằng một mảng W = {H, L, N, B, A, A, A, A} và một chỉ số của dịng cĩ ký tự là ký tự đầu tiên của chuỗi đầu vào; đây là một lựa chọn duy nhất (chỉ cĩ một L trong LAHABANA). Chỉ số này đƣợc gọi là chỉ số chính. Chỉ số K = 1 đƣợc dùng để giải mã.
Output: HLNBAAAA; K=1
Code của quá trình mã hĩa BWT
Dữ liệu đầu vào: mảng S cĩ n phần tử Dữ liệu đầu ra:
W chứa cột cuối cùng của khối sau khi sắp xếp
Chỉ số chính K
Biến sử dụng trong thuật tốn
Thay vì sử dụng n mảng để lƣu trữ n phép quay, ta cĩ thể chỉ sử dụng 1 mảng theo kiểu quay vịng và sắp xếp dữ liệu trên đĩ. Vì vậy p dùng để lƣu trữ tạm chỉ số các giá trị trong mảng S, phục vụ cho quá trình sắp xếp.
Code:
// sanh 2 xau vong s[i..] va s[j..]
inline bool Less(const UC s[], int n, int i, int j)
{ int ik, jk;
for (int k = 0; k < n; ++k) { ik = (i+k)%n; jk = (j+k)%n;
if (s[ik] != s[jk])
return (s[ik] < s[jk]) ? TRUE : FALSE; }
return (i < j) ? TRUE : FALSE; }
void QuickSort(const UC s[], int n, int d, int c) { int i = d, j = c, m = p[(i+j)/2], t;
while (i <= j) {
while (Less(s,n,p[i],m)) ++i; while (Less(s,n,m,p[j])) --j; if (i <= j) { if (i < j) { t = p[i]; p[i] = p[j]; p[j] = t; } ++i; --j; } } if (d < j) QuickSort(s, n, d, j); if (i < c) QuickSort(s, n, i, c); }
int BW(const UC s[], int n, UC w[]) { // int p[MN];
// cout << endl << " Cap phat p: "; cin.get(); int r, i;
for (i = 0; i < n; ++i) p[i] = i; cout << endl << " Call Sort"; QuickSort(s,n,0,n-1);
// BubbleSort(s,p,n);
cout << endl << " End of Sort "; // cin.get(); // for (int i = 0; i < n; ++i) Print(s, n, p[i], "\n");
w[i] = s[(p[i]+(n-1))%n]; if (p[i] == 0) r = i; } w[n] = '\0'; return r; } 2.1.1.2 Biến đổi BWT nghịch
Đầu ra của BWT là cột cuối cùng trong ma trận sắp xếp, gọi đĩ là cột L. Sắp xếp đầu ra của BWT sẽ nhận đƣợc cột đầu tiên của ma trận sắp xếp, gọi là cột F.
1. Sort: AAAABHLN
2. Viết chuỗi L và F cạnh nhau, cùng với các chỉ số:
3. Tạo một "véc tơ chuyển đổi" T[i]
Để tính đƣợc giá trị của T[i], chúng ta dựa vào vị trí của ký tự đĩ trong chuỗi F. Giả sử ký tự đĩ xuất hiện thứ j trong chuỗi F; quét qua chuỗi L để tìm sự xuất hiện thứ j của ký tự đĩ. Khi đĩ vị trí của ký tự này trong L đƣợc ghi nhận là giá trị của T [i]. F[0] A thứ 1 trong F. L[4] A thứ 1 trong L. T[0] = 4 F[1] A thứ 2 trong F. L[5] A thứ 2 trong L. T[1] = 5 F[2] A thứ 3 trong F. L[6] A thứ 3 trong L. T[2] = 6 F[3] A thứ 4 trong F. L[7] A thứ 4 trong L. T[3] = 7 F[4] B thứ 1 trong F. L[3] B thứ 1 trong L. T[4] = 3 F[5] H thứ 1 trong F. L[0] H thứ 1 trong L. T[5] = 0 F[6] L thứ 1 trong F. L[1] L thứ 1 trong L. T[6] = 1 F[7] N thứ 1 trong F. L[2] N thứ 1 trong L. T[7] = 2
Khởi tạo với giá trị của chỉ số chính; L [chỉ số chính] và thiết lập các chỉ số T; lặp lại cho đến khi đạt đƣợc chỉ số chính một lần nữa.
Giải mã: Đọc chỉ số chính. Giá trị: 1 Index = 1 L[1] = L T[1] = 5 Index = 5 L[5] = A T[5] = 0 Index = 0 L[0] = H T[0] = 4 Index = 4 L[4] = A T[4] = 3 Index = 3 L[3] = B T[3] = 7 Index = 7 L[7] = A T[7] = 2 Index = 2 L[2] = N T[2] = 6 Index = 6 L[6] = A T[6] = 1
Index = 1 Primary index reached, end of string. Output: LAHABANA
Ta cĩ thể minh họa quá trình tìm kiếm qua hình 2.3:
Hình 2.3: Mơ tả quá trình biến đ i B T nghịch
Code quá trình biến đổi BWT nghịch
// sanh s[i] s[j]
inline bool Less(const UC s[], int i, int j) { if (s[i] < s[j]) return TRUE;
if (s[i] > s[j]) return FALSE; // s[i] == s[j]
return (i < j) ? TRUE : FALSE; }
void QuickSortw(const UC w[], int d, int c) { int i = d, j = c, m = p[(i+j)/2], t;
while (i <= j) {
while (LESS(w,p[i],m)) ++i; while (LESS(w,m,p[j])) --j; if (i <= j) { if (i < j) { t = p[i]; p[i] = p[j]; p[j] = t; } ++i; --j; } } if (d < j) QuickSortw(w, d, j); if (i < c) QuickSortw(w, i, c); }
UC * WB(const UC w[], int n, int d, UC s[]) { // int p[MN];
int r, i;
for (i = 0; i < n; ++i) p[i] = i; QuickSortw(w,0,n-1);
d = p[d]; for (i = 0; i < n; ++i) { s[i] = w[d]; d = p[d]; } s[n] = '\0'; return s; }
2.1.2 Kỹ thuật biến đổi Move-To-Front (MTF)
MTF là quá trình các ký tự của dữ liệu đầu vào luơn đƣợc dịch chuyển lên đầu danh sách. Điều này d n đến các phần tử giống nhau liền kề nhau sẽ nhận đƣợc giá trị 0 (đầu ra là chỉ số của ký tự trong danh sách lƣu trữ của quá trình thực hiện MTF). Sau quá trình MTF, dãy đƣợc tạo ra dài bằng dãy ban đầu nhƣng sẽ cĩ nhiều giá trị 0 nếu dữ liệu đầu vào cĩ các phần tử giống nhau và đi liền kề với nhau.
Quá trình biến đổi MTF thuận và biến đổi MTF nghịch sẽ đƣợc giải thích thơng qua ví dụ của xâu đầu vàolà xâu đầu ra của BWT: W= “HLNBAAAA”
2.1.2.1 Biến đổi MTF thuận
MTF biến đổi dãy biểu tƣợng đầu vào thành một dãy chỉ số. Với mỗi biểu tƣợng đầu vào của wi, một chỉ số đầu ra đƣợc đƣa ra cho ui. Để tính tốn các giá trị chỉ số, một danh sách các biểu tƣợng bảng chữ cái đƣợc sử dụng, đƣợc sắp xếp bằng các xuất hiện mới nhất của các biểu tƣợng bảng chữ cái. Khi bắt đầu, danh sách đƣợc sắp xếp theo thứ tự từ điển tăng dần. Mỗi lần một biểu tƣợng của wi đƣợc