一 . 基本概念:
赫夫曼樹:給定帶權的N個葉子構成的所有二叉樹中,樹的帶權路徑長度最小的二叉樹(最優二叉樹)
帶權路徑長度:所有樹葉到樹根之間的路徑長度與該節點上權的乘積
權:賦予節點的有意義的參數
二 . 赫夫曼樹的構造
例:設權值集合{2,4,5,7}
1.根據權值進行排序,取最小的兩個葉子(2,4),較小的在左,較大的在右。添加一個新節點,節點的權值爲它倆的權值和,構成一顆二叉樹:
2.用得到的節點作爲新的葉子,重複第一步:
3.一直重複構造,直到所有子葉都取完
所以共需要進行n-1次構造,總共有 n+n-1 = 2n -1 個節點;
可以證明,這樣構造的二叉樹是帶權路徑長度最小的二叉樹
三 . 赫夫曼編碼
1.數據壓縮(編碼):把文件中的每個字符轉換成一個唯一的二進制位串,且串裏面不能包含其它字符的表示串。
2.赫夫曼編碼方法:如上例,設權爲字符在文檔中的重複次數,權值集合爲{A:2,B:4,C:5,D:7},從根節點開始,向左是0,向右是1
那麼
A的編碼爲:110。
B的編碼爲:111。
C的編碼爲:10。
D的編碼爲:0。
3.代碼實現:(C語言)
頭文件等:
#include <stdio.h>
#include <stdlib.h>
#define MAXINT 32767;
typedef struct
{
char Character;
unsigned int Weight;//權重
unsigned int Parent, Lchild, Rchild;//雙親節點,左子節點,右子節點
}HTNode;
a.生成霍夫曼樹:
// n : 總葉子數 m: 用於構造Huffman樹的數組大小(2n-1)
//k, j : 循環變量
//C, W : 接收字符和權時的中轉變量
//Min_w1, Min_w2 : 用於存儲兩個最小權
//p1, p2 : 存儲兩個最小權數組的下標
void Great_Huffman(unsigned int n, HTNode HT[])
{
int k , j;
unsigned int W;
char C;
int m = 2*n - 1;
//輸入n個字符及其權
for(k = 0; k < n; k++)
{
printf("input char and Weight : ");
while((C = getchar()) == '\n');
scanf("%d", &W);
HT[k].Character = C;
HT[k].Weight = W;
HT[k].Parent = HT[k].Lchild = HT[k].Rchild = 0;//初始化
}
//初始化之後用於連接子葉的n-1個節點
for(k = n; k < m; k++)
{
HT[k].Character = '\0';
HT[k].Weight = 0;
HT[k].Parent = HT[k].Lchild = HT[k].Rchild = 0;
}
//構造赫夫曼樹
int Min_w1, Min_w2;
int p1, p2;
//外層循環用於構造新的節點
for(k = n; k < m; k++)
{
Min_w1 = Min_w2 = MAXINT;//初始化最小值
p1 = p2 = 0;//初始化下標
//內層循環用於尋找構造新節點的兩個最小權節點
for(j = 0; j < k; j++)
{
if(HT[j].Parent == 0)//如果這個子葉尚未合併
{
if(HT[j].Weight < Min_w1)//更新最小權
{
Min_w2 = Min_w1;
p2 = p1;
Min_w1 = HT[j].Weight;
p1 = j;
}
else if(HT[j].Weight < Min_w2)//更新次小權
{
Min_w2 = HT[j].Weight;
p2 = j;
}
}
}
HT[p1].Parent = HT[p2].Parent = k;
HT[k].Lchild = p1; HT[k].Rchild = p2;
HT[k].Weight = HT[p1].Weight + HT[p2].Weight;
}
}
(注意輸入格式,字符和權用空格隔開)
:
b.生成霍夫曼編碼
//k : 循環變量
//sp : 編號指針,指向目前編號位,從後往前
//fp :雙親的下標
//p : 當前葉子下標
//HF : 暫時存儲當前子葉的赫夫曼編碼
void Huffman_Coding(unsigned int n, HTNode HT[])
{
int k;
int sp, fp;
char *HF;//當前編碼
HF = (char *)malloc(n*sizeof(char));
int p;
for(k = 0; k < n; k++)
{
sp = n-1; p = k; fp = HT[k].Parent;
//從葉子搜索直到根節點
while(fp != 0)
{
if(HT[fp].Lchild == p)//如果當前節點是左子樹
HF[sp] = '0';
else
HF[sp] = '1';
sp--;
fp = HT[fp].Parent;
p = fp;
}
//顯示編碼
printf("\n%c : ", HT[k].Character);
sp++;
while(sp < n)
{
printf("%c",HF[sp]);
sp++;
}
}
}