Mã hóa Huffman

 
Trong khoa học máy tính và lý thuyết thông tin, mã hóa Huffman là một thuật toán mã hóa dùng để nén dữ liệu. Nó dựa trên bảng tần suất xuất hiện các ký tự cần mã hóa để xây dựng một bộ mã nhị phân cho các ký tự đó sao cho dung lượng (số bit) sau khi mã hóa là nhỏ nhất.
Tác giả.
Thuật toán được đề xuất bởi David A. Huffman khi ông còn là sinh viên Ph.D. tại MIT, và công bố năm 1952 trong bài báo "A Method for the Construction of Minimum-Redundancy Codes". Sau này Huffman đã trở thành một giảng viên ở MIT và sau đó ở khoa Khoa học máy tính của Đại học California, Santa Cruz, Trường Kỹ nghệ Baskin ("Baskin School of Engineering").
Dẫn nhập.
Mã tiền tố.
Để mã hóa các ký hiệu (ký tự, chữ số...) ta thay chúng bằng các xâu nhị phân, được gọi là từ mã của ký hiệu đó. 
Chẳng hạn bộ mã ASCII, mã hóa cho 256 ký hiệu là biểu diễn nhị phân của các số từ 0 đến 255, mỗi từ mã gồm 8 bit. Trong ASCII từ mã của ký tự "a" là 1100001, của ký tự "A" là 1000001. Trong cách mã hóa này các từ mã của tất cả 256 ký hiệu có độ dài bằng nhau (mỗi từ mã 8 bit). Nó được gọi là mã hóa với độ dài không đổi.
Khi mã hóa một tài liệu có thể không sử dụng đến tất cả 256 ký hiệu. Hơn nữa trong tài liệu chữ cái "a" chỉ có thể xuất hiện 1000000 lần còn chữ cái "A" có thể chỉ xuất hiện 2, 3 lần. Như vậy ta có thể không cần dùng đủ 8 bit để mã hóa cho một ký hiệu, hơn nữa độ dài (số bít) dành cho mỗi ký hiệu có thể khác nhau, ký hiệu nào xuất hiện nhiều lần thì nên dùng số bit ít, ký hiệu nào xuất hiện ít thì có thể mã hóa bằng từ mã dài hơn. Như vậy ta có việc mã hóa với độ dài thay đổi. Tuy nhiên, nếu mã hóa với độ dài thay đổi, khi giải mã ta làm thế nào phân biệt được xâu bít nào là mã hóa của ký hiệu nào. Một trong các giải pháp là dùng các dấu phẩy (",") hoặc một ký hiệu quy ước nào đó để tách từ mã của các ký tự đứng cạnh nhau. Nhưng như thế số các dấu phẩy sẽ chiếm một không gian đáng kể trong bảng mã. Một cách giải quyết khác dẫn đến khái niệm mã tiền tố
Đương nhiên mã hóa với độ dài không đổi là mã tiền tố.
Biểu diễn mã tiền tố trên cây nhị phân.
 0/ \1
 A *
 0/ \1
 R Y
Mã tiền tố tối ưu.
Từ ví dụ trên thấy mã hóa của xâu "ARRAY" bằng mã độ dài cố định mất 10 bit, bằng mã tiền tố đã đưa ra mất 8 bit, tiết kiệm được 20%. Bài toán đặt ra là bộ mã tiền tố đã tối ưu chưa.
Bài toán.
Dữ liệu.
Bảng formula_1 chữ cái formula_4.Tập các trọng số (tần suất xuất hiện) tương ứng formula_5, ví dụ: formula_6.
Bộ mã formula_7, là tập hợp các từ mã (nhị phân), trong đó formula_8 là từ mã của formula_9.
Đặt formula_10 là trọng số của bộ mã formula_11. Điều kiện là: formula_12 với mọi bộ mã formula_13.
Giải thuật.
Giải thuật tham lam.
Trong giải thuật tham lam giải bài toán xây dựng cây mã tiền tố tối ưu của Huffman, ở mỗi bước ta chọn hai chữ cái có tần số thấp nhất để mã hóa bằng từ mã dài nhất.
Giả sử có tập A gồm formula_1 ký hiệu và hàm trọng số tương ứng formula_15. 
Như vậy ở mỗi bước số cây bớt đi một. Khi rừng chỉ còn một cây thì cây đó biểu diễn mã tiền tố tối ưu với các ký tự đặt ở các lá tương ứng.
Ví dụ.
Cho bảng tần suất của 5 chữ cái A, B, C, D, E như sau tương ứng là 0.10; 0.15; 0.30; 0.16; 0.29
Quá trình xây dựng cây Huffman diễn ra như sau:
Như vậy bộ mã tối ưu tương ứng là:
Hàng đợi có ưu tiên.
Trong mỗi bước của thuật toán xây dựng cây Huffman, ta luôn phải chọn ra hai gốc có trọng số nhỏ nhất. Để làm việc này ta sắp xếp các gốc vào một hàng đợi ưu tiên theo tiêu chuẩn trọng số nhỏ nhất. Một trong các cấu trúc dữ liệu thuận lợi cho tiêu chuẩn này là cấu trúc đống (với phần tử có trọng số nhỏ nhất nằm trên đỉnh của đống).
Mã giả.
Trong đoạn mã giả dưới đây ta dựa trên một mảng các ký hiệu formula_17 có tần suất tương ứng là formula_18
Tạo hàng đợi bằng đống (heap).
Ta tạo một đống trên cơ sở sắp xếp lại các chỉ số của A và W. Ta lưu trữ đống dưới dạng mảng, ký hiệu nó là "Heap"[1..n]. Trước hết đưa chỉ số của các chữ cái theo thứ tự ban đầu vào mảng "Heap"[1..n] với "Heap"[i]="i". với mọi "i"=1..n.
 Procedure DownHeap(List W,Int k,Int Count)
 "Int" i:=k, v:=W(Heap(k)), j 
 While 2*i&lt;=Count {
 j:=2*i
 if j+1&lt;= Count and W(Heap(j+1))&gt;W(Heap(j)) then j:=j+1
 if W(Heap(j))&lt; v then Heap(i):=Heap(j)
 else break 
 i:=j
 Heap(j):=Heap(k)
 Procedure MakeHeap(List W,Int n)
 "Int" k 
 For k:=int(n/2) downto 1 {
 DownHeap (W,k,n)
Xây dựng cây Huffman.
Ta sẽ lưu trữ cấu trúc của cây mã Huffman vào một mảng. Cây Huffman gồm "n" lá mỗi lá chứa chỉ số của chữ cái tương ứng. Mỗi lần ghép 2 cây thành một ta phải thêm một đỉnh, như vậy cây biểu diễn mã Huffman gồm 2."n"-1 đỉnh. Ta ký hiệu cây này là "Huff"[1..2n-1]. Vì mỗi gốc mới bổ sung đều có trọng số nên ta mở rộng mảng "W"[1..n] các trọng số thành mảng "W' "[1..2n-1]. Gọi m là số đỉnh của cây sẽ xây dựng. lúc đầu ta có n lá, đỉnh bổ sung lần đầu sẽ là n+1, lần thứ 2 là n+2... Khi lấy ra hai ký tự có tần số nhỏ nhất chẳng hạn ký tự thứ i làm con trái và ký tự thứ j làm con phải của đỉnh mới bổ sung có chỉ số m ta đặt "Huff"[i]=-m, "Huff"[j]=m.
 Procedure MakeTreeHuffman("List" W,"Int" n)
 "List" Heap,Huff 
 "Int" i,j,count:=n,m:=n
 MakeHeap(W,n) 
 While Count &gt;1 {
 i:=Heap(1)
 Heap(1):= Heap(count)
 Count:=Count-1
 DownHeap(W,1,Count)
 j:=Heap(1)
 m:=m+1 
 Huff(i):=-m
 Huff(j):=m
 W(m):=W(i)+W(j)
 Heap(1):=m
 DownHeap(W,1,Count) 
 Return Huff
Xây dựng bộ mã.
Sau khi cấu trúc của cây Huffman được lưu vào mảng Huff ta dễ dàng xây dựng mảng Code[1..n] cho bộ mã nhị phân tiền tố tối ưu của các ký tự A[1..n].
 Procedure CodingHuffman("List" Huff, n){
 "Int" k:=1,j
 While k&lt;=n {
 Code(k):=""
 j:=Huff(k)
 While Abs(j)&lt;=2*n-1 {
 If j&gt;0 then Code(k)="1"+Code(k) 
 else Code(k)="0"+Code(k)
 j:=Huff(abs(j)) 
 k:=k+1 
 Return Code
Nén file bằng mã Huffman.
Trong các bước trên, giả sử đã xây dựng được bộ mã Huffman của 256 ký hiệu có mã ASCII từ 0 đến 255 chứa trong mảng Code[1..256].
Việc nén file có thể phân tích sơ bộ như sau: