Lời nói đầu
Ngày nay, cùng với sự phát triển không ngừng của khoa học và công nghệ thì máy tính
đóng vai trò không thể thiếu trong cuộc sống xã hội loài ngời.
Việc trao đổi thông tin của con ngời trong tất cả các ngành, các lĩnh vực của đời sống
ngày càng trở nên cấp thiết và quan trọng, chính vì thế mà các thiết bị thông tin mới
liên tục ra đời nhằm đáp ứng các yêu cầu này. Tuy nhiên, vì một số phần mềm đòi hỏi
rất nhiều bộ nhớ để hoạt động trao đổi thông tin nên ngời ta đã nghĩ ra một phơng
pháp nhằm giải quyết vấn đề này, đó là phơng pháp nén dữ liệu mà vẫn bảo toàn thông
tin.
Nén dữ liệu là một kỹ thuật quan trọng trong rất nhiều lĩnh vực khác nhau. Chính
nhờ có kỹ thuật nén dữ liệu mà ngày nay chúng ta có những phơng tiện truyền thông hiện
đại phục vụ cho cuộc sống nh truyền hình cáp, điện thoại, th điện tử ... và rất nhiều khía
cạnh khác. Do đó kỹ thuật nén dữ liệu ngày càng đợc quan tâm và phát triển nhiều hơn. ở
Việt Nam, hầu hết các trờng Đại học đều quan tâm đến việc nén dữ liệu và điều này đợc
thể hiện ở việc đa kỹ thuật nén trở thành môn học chính thức trong giai đoạn chuyên
ngành .
Trong phạm vi môn học Mã - mã nén . Tôi đa ra bài phân tích trình LZW 15
nhằm mô phỏng thuật toàn kỹ thuật nén dữ liệu.
Tuy nhiên do trình độ còn hạn chế, thời gian và kinh nghiệm cha nhiều, nên bài
phân tích này không thể tránh khỏi sự sai sót trong quá trình phân tích. Do vậy tôi rất
mong đợc sự quan tâm tham gia góp ý Thầy Cô cũng nh cùng toàn thể các bạn Sinh Viên
để bài phân tích này rõ dàng hơn.
Cuối cùng Em xin chân thành cảm ơn thày Nguyễn Lê Anh đã hớng dẫn và giảng
dạy Em trong thời gian qua.
2
Thuật toán nén LZW
Bớc 1 Cắt văn bản mới thành các đoạn copy nếu bảng chữ cái có m chữ thì các chữ cái
là m đoạn copy đầu tiên đợc đánh số từ 0 đến m-1.
Bớc 2 Bỏ tất cả phần chữ thu đợc mã nén.
Lu ý rằng các đoạn copy lần lợt đợc tạo ra và phần số của nó luôn nhỏ hơn số hiệu cột
mà nó đứng.
Thuật toán giải nén LZW.
Bắt đầu là các cột đầu tiên (trong ví dụ là cột thứ 2) lặp lại thao tác sau cho đến hết.
Lấy hai số liên tiếp của bản mã ví dụ là X, Y thay nó về dạng X+? và Y+$. Trong đó
kí tự đầu tiên của Y+$ là kí tự cuối cùng của X+?. Dấu ? và $ là thay cho một kí tự cha
biết. Vì X và Y không thể lớn hơn chỉ số cột mà nó đứng cho nên chúng ta hoàn toàn
tìm đợc giá trị đoạn copy ứng với cột có chỉ số X, Y và thay đoạn copy vào X+? và Y+$
tơng ứng. Giá tri ? là kí tự đầu của Y+$ cho nên luôn luôn xác định. Nh thế chúng ta tìm
đợc X+?.
Ví dụ. Nén theo LZW
Bớc 1 aabababaaababb
thay a0
đợc 0abababaaababb từ điển
đoạn copy mới aabababaaababb
Bớc 2 aabababaaababb
thay a0
đợc 00bababaaababb
từ điển
đoạn copy mới aabababaaababb
Bớc 3 00bababaaababb
thay b1
đợc 001ababaaababb
từ điển
đoạn copy mới aabababaaababb
Bớc 4 001ababaaababb
thay ab3
đợc 0013abaaababb
3
0 a
1 b
0 a
1 b
2 aa
0+a
0 a
1 b
2 aa
0+a
3 ab
0 a
1 b
2 aa
0+a
3 ab
0+b
4 ba
từ điển
đoạn copy mới aabababaaababb
aabababaaababb
Bớc 5 0013abaaababb
thay aba5
đợc 00135aababb
từ điển
đoạn copy mới aabababaaababb
Bớc 6 00135aababb
thay aa2
đợc 001352babb
từ điển
đoạn copy mới aabababaaababb
Bớc 7 001352babb
thay ba4
đợc 0013524bb
từ điển
đoạn copy mới aabababaaababb
Thuật toán LZW
Trong LZW thì token chỉ có index. Để làm việc này, từ điển khi đợc khởi tạo đã gồm
tất cả các ký tự đơn lẻ cho nên nó luôn đợc tìm thấy trong từ điển mặc dù có thể trớc đó
cha xuất hiện trong văn bản.
Thuật toán nén cho LZW:
Khi mới bắt đầu, từ điển đã gồm tất cả các ký tự đơn lẻ.
4
0 a
1 b
2 aa
0+a
3 ab
0+b
4 ba
1+a
0 a
1 b
2 aa
0+a
3 ab
0+b
4 ba
1+a
0 a
1 b
2 aa
0+a
3 ab
0+b
4 ba
1+a
5 aba
3+a
Xâu hiện tại bắt đầu có độ dài 1. Mỗi khi đọc thêm 1 ký tự thì nó đợc thêm vào xâu
hiện tại. Nếu xâu hiện tại còn trùng với một trong các phrase đã có thì quá trình cứ tiếp
tục.
Khi không có phrase trong từ điển trùng với xâu hiện tại nữa thì :
chúng ta cho ra index là chỉ số xâu trớc đó (không kể ký tự vừa đọc vào) trong từ điển.
thêm xâu hiện tại (bao gồm cả ký tự vừa đọc vào) vào trong từ điển.
bắt đầu một xâu mới bằng ký tự vừa đọc vào.
Pseudocode nén của LZW:
Hai lệnh cuối cùng là để xử lý khi hết file, lúc đó cha có phrase mới nên không có
lệnh add_to_dictionary(). Cột đầu tiên là string1 (biến của lệnh add_to_dictionary) bỏ
bớt đi ký tự đầu (ký tự này có do các lệnh string1[0]=character; string1[1]=\0; ở phần
else {} của bớc trớc), đây thực chất là các ký tự đã đợc đọc bởi lệnh
character=getc(input) . Cột thứ hai là index của phrase trong từ điển (ở đây khi phrase
chỉ có 1 ký tự thì chúng ta sử dụng chính ký tự này thay cho index, đúng ra là phải dùng
hàm ascii (ký tự)). Cột thứ 3 là biến (string1 + character) trong lệnh add_to_dictionary
(string1 + character) kèm theo index của nó trong từ điển.
Ví dụ: Mỗi dòng của bảng sẽ ứng với một lần thực hiện vòng lặp
while (!feof(input)) (trừ dòng đầu tiên).
Input String: WED WE WEE WEB WET
Từ thuật toán nén ta thấy rằng:
5
string1[0]=getc(input);
string1[1]=\0;
while (!feof(input)) {
character=getc(input);
if in_dictionary(string1+character)
{string1=string1+character;}
else {
code=look_up_dictionary(string1);
output_code(code);
add_to_dictionary(string1+character);
string1[0]=character;
string1[1]=\0;
}
code=look_up_dictionary(string1);
output_code(code);
- index của string1 ở bớc hiện tại đợc đa vào tệp nén.
- phrase đợc thêm vào từ điển là string1 ở bớc hiện tại + character, trong đó
character là ký tự đầu của string1 ở bớc sau
Thuật toán giải nén nh sau:
Đầu tiên, đọc một index vào và tìm phrase tơng ứng với nó trong từ điển. Đa phrase này
ra tệp (string1 ở bớc trớc).
Vòng lặp:
đọc index tiếp theo, tra từ điển để tìm string1 ở bớc hiện tại, đa nó vào tệp giải nén
thêm (string1 ở bớc trớc + ký tự đầu của string1 ở bớc hiện tại) vào từ điển
Trong đoạn pseudocode sau thì string2 chính là string1 ở bớc trớc:
Ví dụ của việc giải nén:
Trong bảng sau đây, mỗi dòng sẽ tơng ứng với một lần thực hiện vòng lặp while{},
riêng dòng đầu tiên là do các lệnh
string2[0]=input_bits();
string2[1]=\0;
putc(string2[0], output);
Cột thứ nhất (I) là kết quả của lệnh code =input_bits();
Cột thứ hai (II) là kết quả của các lệnh
string1=dictionary_lookup(code);
fputs(string1, output);
Cột thứ ba (III) là biến string2 trong lệnh add_to_dictionary(string2+ string1[0]).
Nó bằng với cột thứ hai (string1) ở bớc trớc do lệnh strcpy(string2, string1).
6
string2[0]=input_bits();
string2[1]=\0;
putc(string2[0], output);
while ((code=input_bits() )!=EOF) {
string1=dictionary_lookup(code);
fputs(string1, output);
add_to_dictionary(string2+string1[0]);
strcpy(string2, string1);}