Từ 1 tới 5 trên tổng số 5 kết quả

Đề tài: Thuật toán nén dữ liệu | Lý thuyết nén dữ liệu Huffman (phần 1: ý tưởng)

  1. #1
    Ngày gia nhập
    05 2007
    Nơi ở
    HCMC
    Bài viết
    60

    Mặc định Thuật toán nén dữ liệu | Lý thuyết nén dữ liệu Huffman (phần 1: ý tưởng)

    Bài này trình bày ý tưởng thuật toán mã hóa Huffman dùng trong nén thông tin. Các bạn có thể gặp nó trong lĩnh vực xử lý ảnh, hoặc trong kỳ thi lấy chứng chỉ… chuẩn kỹ sư CNTT Nhật Bản.

    Mã Huffman là cái gì?

    Đã khi nào bạn đặt câu hỏi: tại sao Winzip nén tập tin “nhatky.doc” 1MB của mình xuống chỉ còn 10KB chưa? Bài viết này giới thiệu khái niệm mã Huffman, một kỹ thuật nén thông tin kinh điển và khá phổ biến trước đây.

    Mã hóa dữ liệu

    Ai cũng biết, để lưu trữ dữ liệu trong máy tính, ta phải mã hóa chúng. Ví dụ, trong bộ mã ASCII nổi tiếng ra đời năm 1967, người ta dùng 8 bit để mã hóa 1 ký tự, cụ thể như:

    “a” = 01100001
    “A” = 01000001

    Người ta gọi dãy số nhị phân trên là từ mã (codeword) của ký tự tương ứng.
    Vậy, với bộ mã ASCII, chúng ta có thể mã hóa 2^8 = 256 ký tự, với mỗi từ mã dài 8 bit.


    Và vấn đề phát sinh từ đây!

    Năm 1952 (lúc đó chưa có ASCII à nghen!), anh sinh viên Huffman nhìn thấy sự lãng phí trong cách lưu trữ dạng này. Ví dụ, để lưu trữ file text có nội dung:

    “Con ca kiem kiem con ca kiem can con ca kiem con”

    Ta thấy trong file trên, chỉ có 8 ký tự được dùng là: C,O,N,A,K,I,M,E
    Vậy, nếu mã hóa bằng mã ASCII, ta phải dùng đến 8 bit chỉ để mã hóa 8 ký tự. Thế thì “giết gà dùng dao mổ trâu” rồi!

    Ý tưởng

    Này nhé, nếu chỉ xét trong nội bộ tập tin, ta thấy: 8 ký tự = 2^3. Như vậy thì chỉ cần 3 bit là đủ để mã hóa chúng rồi. Từ đó, chúng ta tự lập ra 1 bảng mã riêng như vầy:
    “C” = 000
    “O” = 001
    “N” = 010
    “A” = 011
    “K” = 100
    “I” = 101
    “M” = 110
    “E” = 111
    Tuyệt! nếu sử dụng bảng mã “đời mới” này, kích thước tập tin sẽ giảm đi hơn 1 nữa!

    Đơn giản vậy đó hả?

    Chưa đâu, nếu chỉ nhiêu đó thì… level của Huffman chỉ dừng lại ở mức thường thường bậc trung thôi. Ông còn phát hiện thêm điều khác. Quay lại câu “con cá kiếm” lúc nãy, ta nhận thấy ký tự “C” xuất hiện khá nhiều trong câu (8 lần), còn ký tự “M” xuất hiện ít hơn (4 lần). Vậy, nếu ta dùng 1 bit để mã hóa ký tự “C”, và 5 bit để mã hóa ký tự “M”, đại loại như vầy:

    “C” = 0
    “M” = 11111

    Thì khi đó ta sẽ tiết kiệm được thêm: (3 x 8 + 3 x 4) – (1 x 8 + 5 x 4) = 36 – 28 = 8 bít.

    Như vậy, ý tưởng chính ở đây là ta có thể không cần dùng đủ 8 bít để mã hóa cho một ký hiệu. Cần lập bảng mã sao cho độ 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ố bít í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. Tuy nhiên, nếu mã hóa với độ dài thay đổi, thì 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 (“,”) để 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ản mã. Và mã tiền tố là giải pháp phù hợp trong trường hợp này.

    Mã tiền tố là cái gì vậy?

    Mã tiền tố là bộ các từ mã của một tập hợp các kí hiệu sao cho từ mã của mỗi ký hiệu không là tiền tố (phần đầu) của từ mã một ký hiệu khác trong bộ mã đó.

    Ví dụ, muốn mã hóa từ "PETE", với 3 ký tự “E”, “P”, “T”. Ta có:

    - Nếu mã hóa bằng các từ mã có độ dài bằng nhau, ta dùng ít nhất 2 bit cho một ký tự. Chẳng hạn “E”=00, “P”=01, “T”=10. Khi đó mã hóa của cả từ là 0001010010.

    - Nếu mã hóa “E”=0, “P”=01, “T”=11 thì bộ từ mã này không là mã tiền tố. Vì từ mã của “E” là tiền tố của từ mã của “P”.

    - Nếu mã hóa “E”=0, “P”=10, “T”=11 thì bộ mã này là mã tiền tố. Khi đó, để mã hóa xâu “PETE” ta có 01010011.

    Biểu diễn mã tiền tố bằng cây nhị phân

    Một bộ mã tiền tố gồm n lá sẽ được biểu diễn bằng cây nhị phân n lá. Với qui ước, khi duyệt từ nút gốc đến lá, nếu rẽ sang phải là 1, và rẽ sang trái là 0. Ví dụ, bộ từ mã ở trên có thể được biểu diễn như sau:
    Code:
        (*)
     0   |    1
      ---------
     |         |
    (E)       (*)
          0    |   1
           --------
          |        |
         (P)      (T)
    Thuật toán xây dựng mã tiền tố

    Bây giờ chúng ta cùng nghiên cứu thuật toán xây dựng cây mã tiền tố. Đây là bước đầu để xây dựng một bảng mã bảng mã tiền tố tối ưu cho nội dung thông tin mà ta muốn nén.

    Cây mã tiền tố là cây nhị phân. Mỗi nút có thể là nút lá hoặc nút trong (*), kèm theo một con số chứa tần số xuất hiện (trọng số) của nó. Nút lá chứa ký hiệu của chính nó và trọng số, kèm một liên kết đến nút cha. Nút trong chứa trọng số, kèm hai liên kết đến hai nút con, và một liên kết đến nút cha.

    - Bước 0: Khởi tạo một rừng n cây, mỗi cây chỉ có 1 nút gốc và 1 nút lá. Nút lá chứa ký tự, và nút gốc chứa tần số xuất hiện của ký tự đó.

    - Bước 1: Chọn hai cây có trọng số ở gốc nhỏ nhất hợp thành một cây bằng cách thêm một gốc mới nối với hai gốc đã chọn. Trọng số của gốc mới bằng tổng trộng số của hai gốc tạo thành nó.

    - Bước 2: Lặp lại quá trình này đến khi rừng chỉ còn một cây (cây này là kết quả luôn đó!).

    Ví dụ: cho xác xuất của 5 chữ cái a, b, c, d, e 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ư hình sau:

    (sao hổng chèn hình được vầy nè, mod nào hướng dẫn giùm đi!)

    Khi đó, ta có bảng mã tiền tố cho các ký tự là:

    a = 000
    b = 001
    d = 01
    c = 10
    e = 11

    Và giải thuật nén file

    Giả sử đã xây dựng được bộ mã Huffman cho tất cả các ký tự, chúng ta có thể nén file theo giải thuật sau:
    - Đọc từng byte của file cần nén cho đến khi hết tệp
    - Chuyển theo bộ mã thành xâu nhị phân
    - Ghép với xâu nhị phân còn dư từ bước trước
    - Nếu có đủ 8 bit trong xâu thì cắt ra 8 bít đầu ghi vào tệp nén
    - Nếu đã hết tệp cần nén thì dừng

    (còn tiếp)
    Đã được chỉnh sửa lần cuối bởi sonhn : 08-06-2007 lúc 04:07 PM.

  2. #2
    Ngày gia nhập
    08 2006
    Nơi ở
    Hải Phòng
    Bài viết
    218

    Bài viết này hay lắm sonhn cứ tiếp tục đi. Sau này mình sẽ chuyển vào mục Nén tập tin trong [CTDL&GT] cùng các phương pháp khác. Lúc đó tìm hiểu có hệ thống có lẽ sẽ nhiều nguời quan tâm tới đề tài này hơn.

  3. #3
    Ngày gia nhập
    12 2006
    Nơi ở
    US
    Bài viết
    1,917

    Đế tiếp tục bài viết của Sonhn, mình có sưu tầm được 1 đoạn code khá hay và cô đúc, không quá lườm thườm nhưng vẫn thể hiện được ý tưởng. Phần ý tưởng thì như Sonhn đã nêu, sau đây là toàn bộ code viết bằng C++, mình giữ tên nguyên tên tác giả và ko chỉnh sữa gì để tôn trọng công sức của người viết.
    C++ Code:
    1. //------------------------------------------------------
    2. //  
    3. //  this coding is about huffman algorithm.
    4. //  in this coding, i assume that all of you know about
    5. //  huffman algorithm
    6. //
    7. //  Author: Andy Gunawan
    8. //-------------------------------------------------------
    9.  
    10. #include<iostream.h>
    11.  
    12. struct tree
    13. {   int value;
    14.     char ch;
    15.     tree *left;
    16.     tree *right;
    17. };
    18.  
    19. struct input
    20. {   int frequency;
    21.     char character;
    22.     tree *address;
    23. };
    24.  
    25. void postorder(tree *root,int node,char *temp_bit)
    26. {   node++;
    27.     if(root!=0)
    28.     {   temp_bit[node-1]='0';
    29.         postorder(root->left,node,temp_bit);
    30.         temp_bit[node-1]='\0';
    31.        
    32.         temp_bit[node-1]='1';
    33.         postorder(root->right,node,temp_bit);
    34.         temp_bit[node-1]='\0';
    35.  
    36.         if(root->ch!='\0')
    37.             cout<<root->ch<<" : "<<temp_bit<<endl;
    38.     }
    39.     node--;
    40. }
    41.    
    42. tree *inputTree(input num_input1,input num_input2)
    43. {   tree *leftnode = new tree;
    44.     tree *rightnode = new tree;
    45.     tree *root=new tree;
    46.    
    47.     if(num_input1.address=='\0')
    48.     {   leftnode->value = num_input1.frequency;
    49.         leftnode->ch = num_input1.character;
    50.         leftnode->left = '\0';
    51.         leftnode->right = '\0';
    52.     }
    53.     else
    54.     {   leftnode = num_input1.address;  }
    55.    
    56.     if(num_input2.address=='\0')
    57.     {   rightnode->ch=num_input2.character;
    58.         rightnode->value=num_input2.frequency;
    59.         rightnode->left='\0';
    60.         rightnode->right='\0';
    61.     }
    62.     else
    63.     {   rightnode = num_input2.address; }
    64.  
    65.     root->value=num_input1.frequency+num_input2.frequency;
    66.     root->ch='\0';
    67.     root->left=leftnode;
    68.     root->right=rightnode;
    69.  
    70.     return root;
    71. }
    72.  
    73. void bubble(input *number_input,int counter)
    74. {   for(int i=0;i<counter-1;i++)
    75.         for(int j=i+1;j<counter;j++)
    76.             if(number_input[i].frequency > number_input[j].frequency)
    77.             {   input *temp = new input;
    78.  
    79.                 temp->address = number_input[i].address;
    80.                 temp->character = number_input[i].character;
    81.                 temp->frequency = number_input[i].frequency;
    82.                 number_input[i].address = number_input[j].address;
    83.                 number_input[i].character = number_input[j].character;
    84.                 number_input[i].frequency = number_input[j].frequency;
    85.                 number_input[j].address = temp->address;
    86.                 number_input[j].character = temp->character;
    87.                 number_input[j].frequency = temp->frequency;
    88.                
    89.                 delete temp;
    90.             }
    91. }
    92.  
    93. int main(void)
    94. {  
    95.         int number,node=0;
    96.  
    97.     cout<<"How many character will you input? ";
    98.     cin>>number;
    99.    
    100.     char *temp_bit = new char[number];
    101.     input *num_input = new input[number];
    102.  
    103.     for(int p=0;p<number;p++)
    104.     {   num_input[p].address='\0';
    105.         num_input[p].frequency=0;
    106.         num_input[p].character='\0';
    107.     }
    108.    
    109.     for(int i=0;i<number;i++)
    110.     {   cout<<"\nEnter character: ";
    111.         cin>>num_input[i].character;
    112.         cout<<"Enter frequency: ";
    113.         cin>>num_input[i].frequency;
    114.     }
    115.  
    116.     for(int j=0;j<number-1;j++)
    117.     {   bubble(num_input,number);
    118.  
    119.         num_input[j+1].address=inputTree(num_input[j],num_input[j+1]); 
    120.         num_input[j+1].frequency += num_input[j].frequency;
    121.         num_input[j+1].character = '\0';
    122.     }
    123.    
    124.     cout<<endl;
    125.     postorder(num_input[number-1].address,node,temp_bit);
    126.     cout<<endl;
    127.  
    128.     delete temp_bit;
    129.     delete num_input;
    130.     return 0;
    131. }
    Đã được chỉnh sửa lần cuối bởi rox_rook : 07-03-2008 lúc 10:07 AM.

  4. #4
    Ngày gia nhập
    10 2012
    Bài viết
    1

    Mặc định Muốn xác định tần số xuất hiện văn bản có dấu thì sao?

    Vậy còn văn bản có dấu thì làm sao xác định tần số xuất hiện?
    -xuống dòng...

  5. #5
    Ngày gia nhập
    03 2016
    Bài viết
    5

    Code:
    Ví dụ, muốn mã hóa từ "PETE", với 3 ký tự “E”, “P”, “T”. Ta có:
    
    - Nếu mã hóa bằng các từ mã có độ dài bằng nhau, ta dùng ít nhất 2 bit cho một ký tự. Chẳng hạn “E”=00, “P”=01, “T”=10. Khi đó mã hóa của cả từ là 0001010010.
    
    - Nếu mã hóa “E”=0, “P”=01, “T”=11 thì bộ từ mã này không là mã tiền tố. Vì từ mã của “E” là tiền tố của từ mã của “P”.
    
    - Nếu mã hóa “E”=0, “P”=10, “T”=11 thì bộ mã này là mã tiền tố. Khi đó, để mã hóa xâu “PETE” ta có 01010011.
    Ai giải thích giúp em chỗ đổi chuỗi "PETE" sang nhị phân với. Quy luật thế nào nhỉ? Trường hợp 1 sao lại dùng 10 bít trong khi "PETE" có 4 chữ thôi (lẽ ra 8 bit 01001000 mới đúng chứ. Còn trường hợp 3 thì ngu luôn! Số bit lẻ trong khi "PETE" chẵn 4 ký tự

Các đề tài tương tự

  1. Nén thư mục với thuật toán Huffman như thế nào?
    Gửi bởi koutarou trong diễn đàn Thắc mắc lập trình C/C++/C++0x
    Trả lời: 5
    Bài viết cuối: 20-12-2012, 12:27 PM
  2. Cách giải nén bằng thuật toán Huffman
    Gửi bởi tiensjvn trong diễn đàn Thắc mắc lập trình C/C++/C++0x
    Trả lời: 2
    Bài viết cuối: 27-09-2012, 07:11 PM
  3. Thuật toán Huffman.
    Gửi bởi HUMG.ThongIT trong diễn đàn Nhập môn lập trình C#, ASP.NET
    Trả lời: 0
    Bài viết cuối: 24-11-2011, 09:07 PM
  4. chương trình nén file áp dụng thuật toán huffman
    Gửi bởi vinhson trong diễn đàn Dự án & Source code C#, ASP.NET
    Trả lời: 12
    Bài viết cuối: 06-11-2010, 07:38 PM
  5. Hướng dẫn sơ qua về thuật toán nén file Huffman
    Gửi bởi Cpro trong diễn đàn Thắc mắc lập trình C/C++/C++0x
    Trả lời: 1
    Bài viết cuối: 02-12-2008, 06:25 AM

Quyền hạn của bạn

  • Bạn không thể gửi đề tài mới
  • Bạn không thể gửi bài trả lời
  • Bạn không thể gửi các đính kèm
  • Bạn không thể chỉnh sửa bài viết của bạn