霍夫曼編碼原理
在數據通信時,可以用0,1碼的不同排列來表示字符。例如給定一段報文CAST CAST SAT AT A TASA,在報文中出現的字符集合是{C,A,S,T},各個字符出現的頻度是{2,7,4,5}。若給每個字符一個等長的二進制表示,例如 C:00 A:01 S:10 T:11,則所發的報文將是00011011 00011011 100111 0111 01 11011001,共計(2+7+5+4)*2=36個碼。若按字符出現的頻度不同給予不同長度的編碼,出現頻度較大的字符采用爲數較少的編碼,出現頻度較小的字符采用位書較多的編碼,可以是報文的碼數降到最小,這就是所謂的最小冗餘編碼問題。霍夫曼編碼就能實現這種最小冗餘編碼。上例中按字符出現的頻度進行編碼,A:0 T:10 S:110C:111,則最終的報文只有35個碼,節省了傳輸中使用的單元。因而霍夫曼編碼是一種被廣泛應用而且非常有效的數據壓縮技術。
二.編碼實現
一般情況下,霍夫曼編碼的工作主要分爲三步。
第一步是準備工作,對於需要編碼的字符(一般存在於文件裏)進行掃描,統計每個字符出現的頻次,得到一個整數數組。
第二步根據這個頻次數組構造一棵霍夫曼樹,這步是霍夫曼編碼的核心內容。
第三步,再次掃描一遍待編碼的字符,對每個字符,在霍夫曼樹裏搜索該字符,得到它的編碼。
這裏通過C#軟件實現,便於可視化的界面及操作方便性
// 學習小結 吳新強於2013年3月20日16:58:11 桂電 2507
//
//
//
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Collections;
namespace HuffmanCoding
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public struct Node//定義節點結構
{
public int weight; //定義權重
public int parent, lchild, rchild;//定義父親,孩子
};
public struct Min//定義最小兩位數結構
{
public int s1;
public int s2;
};
Node[] HTN = new Node[500];
Min Getmin(int n) //尋找最小的兩位權值
{
int min1, min2, i;
Min code; //定義結構體Min
code.s1 = 1;
code.s2 = 1;
min1 = 256;
min2 = 256;
for (i = 0; i < n; i++)//尋找最小值
{
if (HTN[i].weight <= min1 && HTN[i].parent == 0)
{
min1 = HTN[i].weight;
code.s1 = i;
}
}
for (i = 0; i <= n; i++)//尋找次最小值
{
if (HTN[i].weight <= min2 && i != code.s1 && HTN[i].parent == 0)
{
min2 = HTN[i].weight;
code.s2 = i;
}
}
return code;
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void label1_Click(object sender, EventArgs e)
{
}
private void richTextBox1_TextChanged(object sender, EventArgs e)
{
}
private void richTextBox2_TextChanged(object sender, EventArgs e)
{
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)//編碼響應
{
int length;//統計輸入的字符個數
int Num = 0;//統計輸入字符的種類
int i, j, k, m, start, c, f;
Min min;
int[] weight = new int[256];//用於統計每次字符出現的次數
int[] ind = new int[256];//用於統計i對應的字符
int[] wei = new int[256];//用於統計每個字符權重
string Input = richTextBox1.Text.Trim();//輸入的編碼字符存到Input數組中
string code = "";
if (richTextBox1.Text == "") MessageBox.Show("請輸入編碼字符!");
else
{
length = richTextBox1.Text.Length;
for (i = 0; i < length; i++)
weight[Input[i]]++; //統計每個字符出現的次數即權重
for (i = 0; i < 256; i++)
{
if (weight[i] != 0) //若個數不爲0,將這個字符存入ind數組中,
{
ind[Num] = i; //Ind存放字符代碼
wei[Num++] = weight[i];//相應的權重存放在weight數組中,記錄個數輸入的字符有num種
}
}
m = 2 * Num - 1;
for (i = 0; i < Num; i++)//初始化
{
HTN[i].weight = wei[i];
HTN[i].parent = 0;
HTN[i].lchild = 0;
HTN[i].rchild = 0;
}
for (; i < m; i++)
{
HTN[i].weight = 0;
HTN[i].parent = 0;
HTN[i].lchild = 0;
HTN[i].rchild = 0;
}
for (i = Num; i < m; i++)//建樹
{
min = Getmin(i - 1); //找出最小的兩位數
HTN[min.s1].parent = i;
HTN[min.s2].parent = i;
HTN[i].lchild = min.s1;
HTN[i].rchild = min.s2;
HTN[i].weight = HTN[min.s1].weight + HTN[min.s2].weight;
}
string[] HC = new string[Num];
char[] cd = new char[Num + 5];
for (i = 0; i < Num; i++)//對每個字符進行編碼
{
start = Num + 5;
for (c = i, f = HTN[i].parent; f != 0; c = f, f = HTN[f].parent)
{
if (HTN[f].lchild == c) cd[--start] = '0';
else cd[--start] = '1';
}
for (j = start; j < Num + 5; j++)
HC[i] += cd[j];
}
for (i = 0; i < length; i++)//將輸入字符編程碼
{
k = 0;
int flag = 0;
for (j = 0; j < Num && flag == 0; j++)
{
if (ind[j] == Input[i])//尋找該字符對應的編碼
{
k = j;
flag = 1;
}
}
code += HC[k].ToString();
}
richTextBox2.Text = code;//最終編碼輸出至richTextBox2中
for (i = 0; i < Num; i++)//將每個字符編碼輸出至listbox中
{
this.listBox1.Items.Add(Convert.ToString(Convert.ToChar(ind[i])) + ": " + HC[i]);
}
}
}
private void button2_Click(object sender, EventArgs e)//重新輸入響應
{
richTextBox1.Text = "";
richTextBox2.Text = "";
this.listBox1.Items.Clear();
}
private void button3_Click(object sender, EventArgs e)//退出響應
{
this.Close();
}
}
}
實驗截圖: