Mã Huffman

Một phần của tài liệu MÃ HOÁ NÉN DỮ LIỆU VÀ MÃ HOÁ CÓ KHẢ NĂNG PHÁT HIỆN SAI VÀ SỬA SAI (Trang 40 - 51)

ðây là một trong những phương pháp nén tốt nhất dựa vào những thống kê xác xuất, phương pháp này ñã ñược D. A. Huffman ñưa ra năm 1952 bằng cách tạo một bảng mã cho một tập các kí tự bằng cách dựa vào xác suất của

chúng trong văn bản. Mã Huffman là một mã có tính prefix có ñộ dài từ mã tối thiểu. Mã này thoả mãn 3 tính chất sau:

- Tính cht 1: Tính th t ca ñộ dài t: nếu sắp xếp tin theo thứ tự

xác suất giảm dần pi ≥ pj với i < j thì ñộ dài các từ mã tương ứng phải thoả

nãm ñiều kiện ni≤ nj.

- Tính cht 2: Tínhcht ca nhng t mã cui: bất kì trường hợp nào hai từ mã cuối (số từ mã không nhiều hơn cơ số m của mã) có ñộ dài bằng nhau, về trọng lượng chỉ khác nhau ở trọng lượng kí hiệu cuối). Có n0 từ mã cuối với n0 thoả mãn ñiều kiện 2≤ n0 ≤m.

- Tính cht 3: Tính liên h nhng t mã cui và nhng t mã trước cui: một dãy bất kì gồm n kí hiệu mã thì phải là một từ mã hoặc là prefix của các từ mã cuối.

3.1.3.1. Thut toán lp mã Huffman

Mã Huffman ñược xây dựng theo thuật toán sau: [3]

Bước 1: Sắp xếp các kí tự có trong tập tin theo thứ tự xác suất tăng dần (hoặc giảm dần).

Bước 2: Tính no (số từ mã cuối) theo ñiều kiện: n0 là số nguyên thoả

mãn:     − − ≤ ≤ 1 2 0 0 m n N m n là số nguyên lớn nhất.

Trong trường hợp mã nhị phân thì n0 = 2.

Bước 3: Tiến hành xây dựng cây mã và duyệt cây ñể tìm các từ mã. Vấn ñề then chốt ở ñây là làm thế nào ñể tạo cây mã? Giả thiết ban ñầu có các nút lá, mỗi nút lá tượng trưng cho một kí tự và trọng lượng của nút lá chính là xác suất của kí tự tương ứng.

Huffman ñã ñưa ra thuật toán sau ñể thực hiện tạo cây nhị phân lập mã cho các kí tự C1, C2, …, Cn.

Bước 1: Khởi ñộng danh sách các cây nhị phân một nút chứa trọng số w1, w2, …, wn cho các kí tự C1, C2, …, Cn.

Bước 2: Thực hiện các công việc sau n-1 lần:

- Tìm hai cây T’ và T’’ trong danh sách với các nút gốc có trọng lượng cực tiểu w’ và w’’.

- Thay thế hai cây này bằng một cây nhị phân với nút gốc có trọng lượng w’ + w’’, và có các cây con là T’ và T’’, ñánh dấu các mũi tên chỉñến các cây con 0 và 1 theo thứ tựñó (chẳng hạn cây con trái ñánh số 0, con phải

ñánh số 1).

Bước 3: Mã số của kí tự Ci là dãy các bit ñược ñánh dấu trên ñường từ

gốc của cây nhị phân cuối cùng ñến nút lá Ci.

Như vậy, cây nhị phân theo thuật toán Huffman ñược xây dựng ngược từ

dưới lên (xây dựng dần từ lá), ñây chính là ñiểm khác biệt và cũng chính là

ñiểm mạnh của Huffman so với phương pháp của Fano-Shanon.

Ví d: chúng ta xét 5 kí tự A, B, C, D, E, giả sử rằng chúng xuất hiện theo các xác suất nhưở bảng dưới ñây:

Kí tự A B C D E

Tần số 0,2 0,1 0,21 0,15 0,34

Bước 1: Lập một danh sách các cây nhị phân một nút cho mỗi kí tự (các cây ñã ñược sắp xếp theo chiều trọng số tăng dần). (adsbygoogle = window.adsbygoogle || []).push({});

Bước 2: Hai cây ñầu tiên ñược chọn là các cây tương ứng với B và D, vì chúng có trọng số nhỏ nhất, chúng ñược kết hợp với nhau ñể tạo ra một cây có trọng số 0,1+0,15=0,25 và có hai cây này như là các cây con.

w’ + w’’

T’’ T’’

B D A C E

Từ danh sách 4 cây nhị phân này, ta chọn hai cây có trọng số cực tiểu là cây thứ ba và thứ tư tương ứng với A và C rồi thay thế chúng bằng một cây khác có trọng số 0,2+0,21=0,41 và có hai cây này như là các cây con:

Từ danh sách 3 cây nhị phân này, cây ñầu tiên và cây thứ ba có trọng số

cực tiểu và ñược kết hợp thành một cây nhị phân có trọng số 0,25+0,34=0,59 và có hai cây này như là cây con:

Kết hợp hai cây nhận ñược với nhau, ta ñược cây nhị phân Huffman cuối cùng như hình sau:

Bước 3: Từ cây nhị phân này, duyệt cây ta ñược mã Huffman của các kí tự như sau: B D A C E 0 1 0 1 B D A C E 0 1 0 1 0 1 B D A C E 0 1 0 1 0 1 0 1

Kí tự Mã A 10 B 000 C 11 D 001 E 01 Ta tính ñược n = 0,2*2 + 0,1*3 + 0,21*2 + 0,15*3 + 0,34*2 = 2,25 bit/kí tự Dễ dàng thấy rằng mã Huffman có tính chất giải mã tức thì. Mỗi kí tự

tương ứng với một nút lá trong cây Huffman và chỉ có một ñường duy nhất từ

gốc ñến các nút lá. Vì vậy, không có dãy bit nào vừa là mã số cho một kí tự

vừa là tiền tố của một dãy bit dài hơn biểu diễn một kí tự khác (mã có tính

prefix).

Vì tính giải mã tức thì này, thuật toán giải mã Huffman khá ñơn giản.

3.1.3.2. Thut toán gii mã Huffman

Phương pháp giải mã dựa vào bảng mã ñã ñược lập bằng thuật toán tạo mã, do ñó trong tập tin mã hoá ta sẽ phải lưu trữ thêm thông tin về bảng mã. Quá trình giải mã sẽñọc thông tin của bảng mã này và tiến hành giải mã theo thuật toán sau: [3]

Bước 1: Khởi ñộng con trỏ p chỉñến gốc của cây Huffman.

Bước 2: while (chưa ñạt ñến kết thúc thông báo) làm các bước sau: a. ðặt x là bit tiếp theo trong xâu kí tự.

b. if (x=0) ðặt p bằng con trỏ chỉñến con trỏ bên trái của nó; else ðặt p bằng con trỏ chỉñến bên phải của nó;

c. if (p chỉñến một nút lá)

Hiển thị kí tự tương ứng với nút lá;

ðặt lại p ñể nó chỉñến nút gốc của cây Huffman end if

Ví dụ: Khi nhận ñược bản tin mã: 1 0 0 1 0 0 0 0 0 1 ñã ñược mã hoá bằng cây Huffman ñã xây dựng ở trên. Từ gốc của cây này, ta ñi theo mũi tên

1 0 0 1 0 0 0 0 0 1 A Sau ñó, ta nhận ñược kí tự E (01): 1 0 0 1 0 0 0 0 0 1 A E Sau ñó ta nhận ñược kí tự B (000): 1 0 0 1 0 0 0 0 0 1 A E B Và cuối cùng con ñường 001 dẫn ñến kí tự D: 1 0 0 1 0 0 0 0 0 1 A E B D Nhn xét:

- Ưu ñiểm của phương pháp mã hoá Huffman là dễ xây dựng, ñạt ñược hệ số nén cao, không cần quan tâm ñến ñịnh dạng file cho nên phương pháp Huffman áp dụng ñược cho tất cả các loại file, tuy nhiên hệ số nén tuỳ thuộc vào cấu trúc của các tập tin.

- Nhược ñiểm của phương pháp là bên nhận muốn giải mã ñược thông

ñiệp thì phải có một bảng mã giống như bảng mã ở bên gửi. Vì vậy, như ñã nhắc ñến ở trên, cây Huffman phải ñược lưu vào cùng với tập tin mã hoá. Do

ñó, mã Huffman chỉ có hiệu quảñối với các tập tin dài mà việc tiết kiệm trong tập tin là ñủ ñể bù lại các phí tổn, trong các trường hợp ñó cây Huffman có thểñược tính toán trước và ñược dùng cho một lượng lớn các tập tin. Ví dụ, một cây dựa trên tần số xuất hiện của các chữ cái tiếng Anh có thểñược dùng cho các tài liệu văn bản. Nếu không, hệ số nén ñối với các tập tin bé là không cao, thậm chí tập tin sau khi nén còn lớn hơn tập tin gốc. ðiều này cũng có thể xảy ra khi ai ñó muốn nén một tập tin mà ñã thực hiện nén trước ñó rồi. (adsbygoogle = window.adsbygoogle || []).push({});

Phương pháp Huffman sẽ trở nên cồng kềnh khi số tin quá lớn. Trong trường hợp này người ta dùng một biện pháp phụ ñể giảm nhẹ quá trình mã hoá như sau: Trước tiên liệt kê các tin của nguồn theo thứ tự xác suất giảm dần, sau ñó ghép thành nhiều nhóm tin có xác suất gần bằng nhau. Dùng một

mã ñều ñể mã hoá các tin trong cùng một nhóm. Sau ñó xem các nhóm tin như một khối tin và dùng phương pháp Huffman ñể mã hoá các khối tin. Từ

mã cuối cùng tương ứng mỗi tin của nguồn gồm 2 phần: một phần là mã Huffman, một phần là mã ñều.

3.1.3.3. Cài ñặt thut toán Huffman

Chương trình cài ñặt mô phỏng thuật toán Huffman bằng ngôn ngữ lập trình C ñược xây dựng như sau:

Nén d liu

Chương trình sử dụng ñến cấu trúc dữ liệu con trỏ của ngôn ngữ C và dùng mô hình cây nhị phân ñể tiến hành lập mã Huffman.

Cấu trúc dữ liệu ñược dùng trong chương trình ñược mô tả như sau: typedef struct chardata{

short charnum;//Lưu trữ kí hiệu

unsigned long total;//Lưu trữ tổng số kí hiệu short seq_length;//ðộ dài bit

short bit_sequence;//Bit tiếp double frequency;//Tần số

struct chardata *up, *left, *right;//Các con trỏ

};

Cấu trúc này dùng ñể lưu trữ thông tin cho một kí hiệu và dựa vào ñó ñể

tiến hành lập cây.

ðể có thể tiến hành giải mã bản tin, ta cũng cần lưu thêm một bảng chứa các thông tin cần ñể giải mã như kí hiệu và một số thông tin khác cần cho việc xử lí, mô tả như sau:

//Cấu trúc bảng giải mã

typedef struct decode_table_element{

unsigned char letter; //Kí tự giải mã short left;//Số lượng nút bên trái short right;//Số lượng nút bên phải

};

Mô tả hoạt ñộng của chương trình như sau: trước hết, chương trình sẽ ñọc nội dung của tập tin input, phân tích và lập bảng tần số cho các kí tự có trong tập tin. Vì thông tin lưu trong file ñược ñọc dưới dạng nhị phân (mỗi

lần ñọc 1 byte - tức 8 bit), do ñó, ta cũng chỉ cần xây dựng tần số cho tối ña là 256 kí tự mà thôi. Tiếp ñó, khi ñã tạo xong bảng tần số, chương trình sẽ

tiến hành sắp xếp bảng tần số theo chiều tần số tăng dần. Công việc tạo cây nhị phân ñược thực hiện theo thuật toán ñã nêu ở trên ñược thực hiện ngay sau ñó, khi ñã có ñược cây nhị phân, chương trình sẽ tiếp tục làm hai công việc: tạo bảng giải mã (phục vụ cho giải mã) và tiến hành duyệt cây ñể tạo mã, chương trình sẽ ñọc cây liên tục cho ñến khi ñủ 8 bit thì tiến hành ghi vào file output, sau ñó, quá trình cứ tiếp tục như vậy cho ñến khi ñọc hết cây nhị phân.

Hoạt ñộng này ñược thực hiện bằng ñoạn mã sau:

for (c=0;c<256;c++) //lần lượt ghi thông tin cho từng kí tự { huftable[c]->charnum=c; huftable[c]->total=0L; huftable[c]->frequency=0; huftable[c]->up=NULL; huftable[c]->left=NULL; huftable[c]->right=NULL; } while (!feof(infile)) { c=fgetc(infile); if (feof(infile)) continue; ++total; ++huftable[c]->total; } if (total==0L) { puts("Tệp rỗng."); return 0; } fclose(infile); //Tiến hành sắp xếp qsort((void*)huftable,(size_t)256,(size_t)sizeof(struct chardata *),compare); dumptable(); /// Tạo cây nhị phân if (create_tree()!=TRUE) return 1; //ðọc mã của từng kí tự gen_bit_sequences(root); puts("Tạo bảng giải mã..."); if (create_decode_table()==FALSE) { puts("Không ñủ bộ nhớ ñể tạo bảng.");

return 1; } printf("Tạo bảng thành công, bảng có %d phần tử.\n", array_max_index+1); //Thực hiện nén tệp tin if (compress()!=0) return 1; return 0;

Hàm create_tree() Dùng ñể tạo cây Huffman theo thuật toán ñã nên ở trên, chi tiết hàm này như sau:

/// Tạo cây nhị phân struct chardata *ptr1, *ptr2; short create_tree() { void find_lowest_freqs(void); short complete(void); double maxfreq=0; (adsbygoogle = window.adsbygoogle || []).push({});

struct chardata *new_node=NULL; while (maxfreq<0.99999)

{

//Tìm cây có tần số thấp nhất find_lowest_freqs();

new_node=(struct chardata *)malloc(sizeof(struct chardata));; new_node->up=NULL; new_node->left=ptr2; new_node->right=ptr1; new_node->charnum=-1; ptr1->up=new_node; ptr2->up=new_node; new_node->frequency=ptr1->frequency+ptr2->frequency; maxfreq=new_node->frequency; }

Hàm gen_bit_sequences(node) duyệt cây và tạo ra dãy bit biểu diễn cho từng kí tự, cài ñặt chi tiết của hàm như sau:

//Tạo các dãy bit

void gen_bit_sequences(struct chardata *node) {

unsigned short asctobin(char *bit_seq); static char bit_sequence[16];

double frac; if (node->left!=NULL) { bit_sequence[seq_len++]='1'; gen_bit_sequences(node->left); seq_len--; }

if (node->right !=NULL) { bit_sequence[seq_len++]='0'; gen_bit_sequences(node->right); seq_len--; } if (node->right!=NULL) return; bit_sequence[seq_len]=0; node->seq_length=(long)seq_len; //ðổi ra bit node->bit_sequence=asctobin(bit_sequence); frac=(((double)huftable[node->charnum]- >total))/(double)total; }

Khi tạo file nén, chương trình sẽ thực hiện ghi thêm các thông tin về file như: tên file gốc, kích thước của file gốc, bảng giải mã. Tên file gốc có ñộ dài tối

ña 255 kí tự, chương trình dành 1 byte ñể lưu ñộ dài của tên file gốc ñể trình giải mã dễ dàng ñọc ñược tên của file gốc. Kích thước của file gốc ñược lưu lại ñể

trình giải nén kiểm tra xem số lượng byte ñược giải nén có ñúng bằng tổng số

byte ban ñầu hay không.

ðiều ñáng lưu ý ởñây là thông tin ñược lưu trong bảng giải mã: ngoài kí tự, bảng giải mã còn chứa thêm các trường là leftrightñể chứa số lượng các nút

ở bên trái (phải) của nút ñang xét (hay nói cách khác, cây Huffman ñã ñược lưu vào trong bảng giải mã), khi giải nén ta chỉ cần lần theo các trường này và dễ

dàng tìm ra kí tựñược giải mã.

Gii nén

Quá trình giải nén ñược thực hiện theo các công ñoạn: trước hết chương trình sẽ ñọc phần header của file nén ñể lấy các thông tin như tên của file, kích thước gốc của file và quan trọng là bảng giải mã (hay cũng chính là cây Huffman). Tiếp theo, chương trình sẽ ñọc bảng giải mã và tiến hành giải nén tập tin, các công việc này ñược thực hiện như thuật toán giải mã ñã trình bày

ở trên.

Hàm short read_header(void) làm nhiệm vụ ñọc phần ñầu của file nén ñể lấy ra tên file gốc, kích thước file gốc và bảng giải mã.

Công việc giải nén ñược thực hiện thông qua hàm short uncompress(void) bằng cách lấy ra từng kí tự trong file nén, thực hiện

dịch trái từng bit, mỗi lần dịch như vậy sẽ kiểm tra xem dãy bit có là một kí tự

nào không, nếu ñúng (tức left của nút ñó bằng 0) thì ghi ra kí tự và chuyển về

gốc của cây, còn không thì tiếp tục dịch thêm bit và lần theo các con trỏ left hoặc right ñể tiếp tục kiểm tra. Cứ như vậy cho ñến khi số byte giải nén ñược

ñúng bằng kích thước ban ñầu của file gốc.

Cần nhắc lại là với cấu trúc của bảng giải mã như trên thì tốc ñộ giải nén sẽ ñược tăng lên, bởi mỗi khi ñọc ñược một bit thì chương trình sẽ duyệt cây và tìm ra ngay kí tự giải mã, con trỏ duyệt cây chỉ ñược ñưa về gốc sau khi một kí tựñã ñược xuất ra.

ðoạn chương trình này mô tả việc dịch trái một bit và kiểm tra xem dãy bit có là biểu diễn của kí tự nào hay không (việc dịch các bảy bit còn lại ñược thực hiện tương tự):

while (1) //Chừng nào còn dữ liệu trong file { buffer=fgetc(infile); index=((buffer&0x0080)?fastleft:decode_table_de[index].right); buffer <<=1; fastleft=decode_table_de[index].left; if (fastleft==0) { if (fputc((int)decode_table_de[index].letter,outfile)==EOF) { (adsbygoogle = window.adsbygoogle || []).push({});

puts("Lỗi khi ghi vào file. Có thể ñĩa ñầy?"); return 1;

} index=0;

fastleft=fastleft0;

if (++running_total==total) goto finished; }

{...}

ðánh giá hiu qu ca chương trình

Chương trình nén Huffman ñã ñược sử dụng ñể nén cho nhiều loại file khác nhau: file âm thanh, file hình ảnh, file văn bản…, bảng dưới ñây mô tả

kết quả thử nghiệm của chương trình trên các dạng file, ñồng thời chúng tôi cũng dùng Winrar - một chương trình nén file rất mạnh và thông dụng trên

Một phần của tài liệu MÃ HOÁ NÉN DỮ LIỆU VÀ MÃ HOÁ CÓ KHẢ NĂNG PHÁT HIỆN SAI VÀ SỬA SAI (Trang 40 - 51)