Phương pháp nén LZW ñược ñưa ra giới thiệu năm 1984 trên cơ sở Terry Welch ñã tối ưu phương pháp LZ78. ‘ ‘ 8 ‘A‘ 2 ‘ ‘ 3 ‘ ‘ 3 ‘D‘ 6 ‘O‘ 9 ‘Y‘ 7 ‘A‘ 4 ‘ ’ D 0
LZW hoạt ñộng dựa trên một ý tưởng rất ñơn giản là người mã hoá và người giải mã cùng xây dựng bảng mã. Phương pháp nén LZW thực hiện thay thế một xâu kí tự bằng một mã ñơn mà không phải thực hiện bất kì sự
phân tích nào ñối với dữ liệu vào. Nó chỉ việc thực hiện thêm một xâu mới mà nó tìm thấy vào một mảng các chuỗi kí tự (từ ñiển). Dữ liệu ñầu vào
ñược nén khi xuất hiện một mã ñơn ở ñầu ra thay thế cho một chuỗi kí tự
(ñã có trong từñiển). [18]
3.2.3.1. Phương pháp nén LZW
Giải thuật ñể lập mã LZW rất ñơn giản, nguyên tắc hoạt ñộng của nó như
sau:
- Một xâu kí tự là một tập hợp từ hai kí tự trở lên.
- Nhớ tất cả các xâu kí tựñã gặp và gán cho nó một dấu hiệu riêng.
- Nếu lần sau gặp lại xâu kí tự ñó, xâu kí tự sẽ ñược thay thế bằng dấu hiệu của nó.
Phần quan trọng nhất của phương pháp này là phải tạo một mảng lớn dùng ñể lưu trữ các xâu kí tự ñã gặp (từ ñiển). Khi các byte dữ liệu cần nén
ñược ñem ñến, chúng liền ñược giữ lại trong bộ ñệm và ñem so sánh với các chuỗi ñã có trong từñiển. Nếu chuỗi dữ liệu trong bộ ñệm không có trong từ ñiển thì nó ñược bổ sung thêm vào từñiển và chỉ số của chuỗi ở trong từñiển chính là dấu hiệu của chuỗi. Nếu chuỗi trong bộ ñệm ñã có trong từ ñiển thì dấu hiệu của chuỗi ñược ñem ra thay cho chuỗi ở dòng dữ liệu ra.
Có bốn quy tắc ñể thực hiện việc nén dữ liệu theo thuật toán LZW là:
Quy tắc 1: 256 dấu hiệu ñầu tiên ñược dành cho các kí tựñơn (0-0FFh).
Quy tắc 2: Cố gắng so sánh với từ ñiển khi trong bộ ñệm chứa ñã có nhiều hơn hai kí tự.
Quy tắc 3: Các kí tựở ñầu vào ñược bổ sung vào bộñệm ñến khi chuỗi kí tự trong bộñệm không có trong từñiển.
Quy tắc 4: Khi bộ ñệm có một chuỗi mà trong từñiển không có thì chuỗi trong bộ ñệm ñược ñem vào từñiển. Kí tự cuối cùng của chuỗi kí tự trong bộ ñệm chứa phải ở lại trong bộñệm ñể tiếp tục tạo thành chuỗi mới.
Thuật toán nén dữ liệu LZW:
LZW_Compress
String = lấy một kí tự vào;
While (vẫn chưa hết kí tự ở tập tin vào)
Character = lấy một kí tự vào;
If (String+Character nằm trong từ ñiển)
String = String + Character; Else
Xuất ra mã cho String;
ðưa String+Character vào từ ñiển;
String=Character; End if
End while
Xuất ra mã của String;
Ví dụ: Chúng ta cần mã hoá một ñoạn văn bản bằng tiếng Anh sau (giữa các từ ngăn cách nhau bởi dấu ‘/’): “/WED/WE/WEE/WEB/WET”
Bước 1: Gán cho 256 kí tự trong bảng mã các mã từ 0 ñến 255.
Bước 2: Kí tự thứ nhất ‘/’ ñược cất vào bộñệm chứa ñể chuẩn bị tạo nên một chuỗi.
Bước 3: Kí tự thứ hai ‘W’ ñược nối thêm vào sau kí tự ‘/’. Vì trong từ ñiển chưa có chuỗi “/W” nên chuỗi này ñược thêm vào từ ñiển và mã cho kí tự ‘/’ ñược ñưa ra (256).
Bước 4: Kí tự thứ ba ‘E’ ñược ñọc ñến, chuỗi thứ hai “WE” ñược ñưa vào từñiển và mã cho kí tự W ñược ñưa ra (257).
Bước 5: Kí tự thứ tư ‘D’ ñược ñọc vào bộ ñệm, chuỗi “ED” ñược ñưa vào từñiển và mã cho kí tự E ñược ñưa ra (258).
Bước 6: Kí tự thứ năm ‘/’ ñược ñọc vào bộ ñệm, chuỗi “D/” ñược ñưa vào bộñệm và mã cho kí tự D ñược ñưa ra (259).
Bước 7: Kí tự thứ sáu ‘W’ ñược ñọc vào bộ ñệm, chuỗi “/W” ñã ñược phát hiện có trong từñiển nên không có mã nào ñược ñưa ra, “/W” tiếp tục ở
trong từñỉển ñể tạo chuỗi mới.
Bước 8: Kí tự thứ bày ‘E’ ñược ñưa vào bộ ñệm, khi ñó “/W” ñược thay thế bằng 256 và ñược ñưa ra khỏi bộñệm, “/WE” không có trong từ ñiển nên “/WE” ñược ñưa vào trong từñiển và tiếp tục xử lí.
Quá trình trên cứ tiếp tục cho ñến khi hết xâu vào và mọi mã ñều ñược
ñưa ra. Việc giảm kích thước chỉ thực sự bắt ñầu tại bước 7 khi mà một dấu hiệu 256 dã ñưa ra thay thế cho “/W”.
Chuỗi vào = /WED/WE/WEE/WEB/WET Kí tự vào Mã xuất ra Giá trị mã mới Từñiển /W / 256 /W E W 257 WE D E 258 ED / D 259 D/ WE 256 260 /WE / E 261 E/ WEE 260 262 /WEE /W 261 263 E/W EB 257 264 WEB / B 265 B/ WET 260 266 /WET EOF T Bảng 3.10. Quá trình thực hiện giải thuật nén LZW Kết quảñưa ra là xâu: / W E D 256 E 260 261 257 B 260 T
Bảng 3.10 trên ñây mô tả quá trình thực hiện giải thuật trên xâu nhập vào. Có thể thấy, từ ñiển ñầy lên một cách nhanh chóng khi một xâu mới
ñược ñưa vào từ ñiển và một mã ñược ñưa ra. Trong ví dụ này, có 5 mã thay thế cho các xâu kí tự ñược ñưa ra, nếu chúng ta sử dụng mã 9 bit cho
ñể lưu kết quả thì với 19 kí tự trong xâu vào trên sẽ giảm ñược 13 byte ở
xâu kết quả. Tất nhiên, ví dụ này ñược chọn ñể cho thấy sự thay thế mã, trong thực tế sử dụng thì có khi phải sau hàng trăm hoặc nhiều hơn byte mới có ñược sự thay thế.
Trong thuật toán nén này, phần lớn thời gian khi bắt ñầu nén chủ yếu mất vào việc tạo từ ñiển, khi từ ñiển ñủ lớn, xác suất gặp chuỗi ở bộ ñệm chứa trong từñiển tăng lên và càng nén ñược nhiều hơn. Một ñiều cần chú ý ở ñây là mỗi một dấu hiệu, ta phải lưu một chuỗi trong từ ñiển ñể so sánh. Vì dấu hiệu ñược biểu diễn bằng một số 12 bit nên từ ñiển sẽ có 4096 lối vào, khi tăng số bit ñể biểu diễn dấu hiệu lên thì hiệu quả nén sẽ tốt hơn nhưng lại bị
giới hạn bởi bộ nhớ máy tính. Chẳng hạn, khi ta dùng 16 bit ñể biểu diễn một dấu hiệu thì từñiển phải có ñến 65536 lối vào, nếu mỗi lối vào có khoảng 20 kí tự thì từ ñiển phải lớn khoảng 1,2MB. Với một từ ñiển có dung lượng như
vậy thì rất khó có thể thực hiện trên các máy tính PC hoạt ñộng dưới hệ ñiều hành DOS vì giới hạn của một segment là 65KB. [18]
3.2.3.2. Phương pháp giải nén LZW
Kĩ thuật giải nén của LZW rất hiệu quả và tiện lợi, ñó là bên giải mã có thể tự xây dựng bảng mã trong quá trình giải mã mà không cần phải lưu lại trước ñó. Bảng giải mã ñược xây dựng bằng chính các dữ liệu ñã thực hiện nén. Có thể lí giải ñiều này bởi trong giải thuật nén thì luôn luôn ghi ra các xâu và các kí tự, quá trình giải nén sẽ sử dụng chính các xâu, kí tự này ñể giải nén, ñiều ñó có nghĩa là file nén không phải ghi thêm các thông tin giúp giải nén (mà thường là rất lớn). Giải thuật giải nén LZW: LZW_DeCompress ðọc OLD_CODE; Xuất ra OLD_CODE; While (còn chưa hết file) ðọc NEW_CODE
Xuất ra String;
Character = kí tự ñầu tiên trong String;
ðưa OLD_CODE + CHARACTER vào từ ñiển giải mã;
OLD_CODE = NEW_CODE; End While
Giải thuật này giống như giải thuật nén, nó thêm một xâu mới vào từ ñiển giải mã mỗi khi ñọc một mã mới. Cần phải dịch mỗi mã ñưa vào thành một xâu kí tự và ñưa ra file kết quả.
Bảng 3.11 dưới ñây mô tả quá trình giải nén xâu “/ W E D 256 E 260 261 257 B 260 T” ñược nén ở ví dụ trên. Mã ñưa vào: / W E D 256 E 260 261 257 B 260 T ðầu vào/ NEW_CODE OLD_CODE STRING/ ðầu ra CHARACTER Từñiển giải mã / / / W / W W 256 = /W E W E E 257 = WE D E D D 258 = ED 256 D /W / 259 = D/ E 256 E E 260 = /WE 260 E /WE / 261 = E/ 261 260 E/ E 262 = /WEE 257 261 WE W 263 = E/W B 257 B B 264 = WEB 260 B /WE / 265 = B/ T 260 T T 266 = /WET Bảng 3.11. Quá trình thực hiện giải thuật giải nén LZW
Tuy nhiên, phương pháp giải nén ở trên quá ñơn giản, bảng 3.11 chỉ là một ví dụ quá nhỏ bé, chưa kiểm soát hết các trường hợp. Có một vài trường hợp ngoại lệ trong giải thuật nén LZW gây khó khăn cho quá trình giải nén. Chẳng hạn một chuỗi gồm có (String, Character) ghép ñôi ñược ñịnh nghĩa trong từ ñiển, và trong dòng dữ liệu vào có một dãy của String, Character, String, Character, khi ñó, giải thuật nén sẽ ñưa ra một mã trước khi bộ giải nén có cơ hội ñịnh nghĩa nó. Giả sử chuỗi JOEYN ñược ñịnh nghĩa trong từ
ñiển có mã là 300. Sau ñó, dãy JOEYNJOEYNJOEY xuất hiện trong từ ñiển, quá trình nén sẽ tạo ra mã như hình dưới ñây:
Input String: ...JOEYNJOEYNJOEY Character Input New Code/String Code Output
JOEYN 300 = JOEYN 288 (JOEY) A 301 = NA N
. . .
. . .
. . .
JOEYNJ 400 = JOEYNJ 300 (JOEYN) JOEYNJO 401 = JOEYNJO 400 (???)
Bảng 3.12. Một vấn ñề gặp phải
Khi giải thuật giải mã ñọc ñược xâu này trong file nén, ñầu tiên nó sẽ
giải mã 300 và ñưa ra xâu JOEYN. Sau khi ñưa ra file kết quả, nó sẽ thêm
ñịnh nghĩa của mã 399 vào từñiển, rồi nó ñọc ñến mã tiếp theo, 400, và phát hiện ra rằng không còn gì ở trong từ ñiển. ðây thực sự là một vấn ñề, liệu có cách nào ñể xử lí.
May mắn thay, ñây là trường hợp duy nhất mà giải thuật giải nén sẽ gặp một mã không xác ñịnh. ðể giải quyết vấn ñề này, chúng ta sẽ thêm một ñiều khiển ngoại lê vào giải thuật. Thuật giải sẽ tìm trường hợp ñặc biệt này và xử
lí nó. Trong ví dụở bảng 3.12, giải thuật giải nén sẽ tìm thấy một mã của 400, và không xác ñịnh ñược, nó sẽ dịch giá trị của OLD_CODE (là 300), sau ñó thêm Character là ‘J’ vào trong xâu. Và kết quả ñúng của mã 400 là “JOEYNJ”.
Từñó, ta có giải thuật giải nén ñược sửa ñổi nhưở dưới ñây:
LZW_DeCompress ðọc OLD_CODE; Xuất ra OLD_CODE; CHARACTER = OLD_CODE; While (chưa hết file nén) ðọc NEW_CODE;
If(NEW_CODE không nằm trong từ ñiển)
STRING = STRING+CHARACTER; Else
STRING = dịch NEW_CODE ra kí tự ban ñầu;
End if
Xuất ra STRING;
CHARACTER = kí tự ñầu tiên trong STRING;
Thêm OLD_CODE + CHARACTER vào từ ñiển;
OLD_CODE = NEW_CODE; End while
3.2.3.3. Cài ñặt thuật toán LZW
Chương trình cài ñặt mô phỏng thuật toán LZW bằng ngôn ngữ lập trình C ñược xây dựng như sau:
Chương trình ñược chia làm hai phần chính: phần modul thực hiện nén dữ liệu và phần modul thực hiện giải nén.
Nén dữ liệu
Chương trình sử dụng mã có ñộ dài 12, 13, 14 … bit, sự khác nhau của các ñộ dài mã ở chỗ: chẳng hạn ñối với mã ñộ dài 12 bit thì có thể có ñược 4096 chuỗi trong từ ñiển. Như trên ñã trình bày, mỗi khi có một kí tự mới
ñược ñọc vào, từ chương trình sẽ tìm kiếm trong từñiển, nếu không tìm thấy thì xâu mới sẽ ñược ñưa vào từ ñiển. Có hai vấn ñề nảy sinh. Thứ nhất, từ ñiển có thể lớn rất nhanh, khi ñó thậm chí ñộ dài trung bình chỉ là 3 ñến 4 kí tự trong một xâu nhưng khi xuất ra mã cũng có thể lên tới 7 hoặc 8 byte cho mỗi mã (khi ñó thì thực sự nén không ñạt yêu cầu - file nén có kích thước còn lớn hơn cả file chưa ñược nén). Thứ hai là việc tìm kiếm các xâu kí tự trong từ ñiển, mỗi khi một kí tự mới ñược ñọc vào, chương trình sẽ tìm kiếm một xâu mới có dạng String + Character. Việc tìm kiếm mỗi xâu sẽ phụ thuộc vào chiều dài mã. Chẳng hạn, sử dụng 12 bit có nghĩa là có thể sẽ phải thực hiện 12 lần so sánh cho mỗi mã.
Phương pháp lưu trữ mới của chúng ta nhằm giảm bớt thời gian so sánh các xâu tuy nhiên không giảm số lần so sánh cần thiết. Chúng ta không lưu trữ 256 mã trong một mảng cố ñịnh mà lưu trữ chúng trong mảng dựa vào ñịa chỉ ñược hình thành từ bản thân chuỗi. Khi chúng ta tìm kiếm một chuỗi, ta
có thể sử dụng một xâu thử ñể phát sinh một ñịa chỉ và ta có thể tìm thấy xâu trong một lần tìm kiếm. Như vậy, mã của một xâu không phải chỉ là vị trí trong mảng, mà ta cần lưu trữ mã của xâu cùng với xâu ñó. Ở ñây, ta sử dụng 3 dữ kiện cho một xâu, ñó là code_value[i], prefix[i] và appen_character[i]. Khi muốn thêm một xâu mới vào từ ñiển, ta sử dụng hàm find_match ñể
tìm ra một vị trí chính xác của i, trước tiên hàm find_match tạo ra một ñịa chỉ và kiểm tra xem vị trí này ñã có xâu nào chưa, nếu có rồi nó sẽ thực hiện lần dò thứ hai cho ñến khi thấy một vị trí trống. ðể ñảm bảo cho lần dò thứ
hai luôn làm việc thì kích thước của từ ñiển phải là một số nguyên tố. ðiều này do lần dò thứ hai của hàm find_match có thể là một số nguyên nằm trong khoảng từ 0 ñến hết kích thước của từñiển. Nếu việc dò và kích thước từ ñiển không phải là nguyên tố, việc tìm kiếm một vị trí trống có thể sẽ
không thành công thậm chí vẫn còn có vị trí trống. [18]
Mô hình của ñoạn chương trình thực hiện nén dữ liệu như sau: //Chương tính thực hiện nén file_in và xuất ra file_out int compress(char *file_in, char *file_out)
{ code_value=(int*)malloc(TABLE_SIZE*sizeof(int)); prefix_code=(unsigned int*)malloc(TABLE_SIZE*sizeof(unsigned int)); append_character=(unsigned char*)malloc(TABLE_SIZE*sizeof(unsigned char)); if(code_value==NULL || prefix_code==NULL || append_character==NULL) { printf("Không thể cấp phát bộ nhớ!"); exit(-1); }
//Lấy tên file, mở và ghi ra tệp tin nén //Thực hiện nén file
//Lưu tên file vào phần ñầu file out
//Thực hiện tách tên file ñầy ñủ lấy tên file ñể ghi vào Header
//Ghi tên file vào phần header
next_code=256; //Mã tiếp theo là mã xâu c"n lại for(i=0;i<TABLE_SIZE;i++)/* Xoá rỗng từ ñiển*/
code_value[i]=-1; i=0;
string_code=getc(input_file); //Lấy mã ñầu tiên //Vòng lặp cho ñến khi mọi mã ñã ñược ñịnh nghĩa while ((character=getc(input_file))!=(unsigned)EOF) {
//Tìm mọi xâu trong bảng. Nếu có thì lấy giá trị của