一、哈夫曼樹的定義
在許多應用中,樹中結點常常被賦予一個表示某種意義的數值,稱為該結點的權。從樹的根到任意結點的路徑長度(經過的邊數)與該結點上權值的乘積,稱為該結點的帶權路徑長度。樹中所有葉子結點的帶權路徑長度之和稱為樹的帶權路徑長度,記作:
\[WPL=\sum\limits_{i=1}^n{w_i}{l_i} \]
在含有n個葉子結點的二叉樹中,其中帶權路徑長度(WPL)最小的二叉樹為哈夫曼樹。
二、哈夫曼樹的構造
構造哈夫曼樹的算法描述如下:
1)將這n個結點分別作為n棵僅含一個結點的二叉樹,構成森林F。
2)構造一個新的結點,從F中選取兩棵根結點權值最小的樹作為新結點的左右子樹,並且將新結點的權值置為左右子樹上根結點的權值之和。
3)從F中刪除剛才選出的兩棵樹,同時將新得到的樹加入F中。
4)重復2)和3),直到F中只剩下一棵樹為止。
哈夫曼樹的特點
:
- 每個初始結點最終都成為葉子結點,且權值越小的結點到根結點的路徑長度越大
- 構造過程中新建了n-1個結點,因此哈夫曼樹的結點總數為2n-1
- 每次構造都選擇2棵樹作為新結點的孩子,因此哈夫曼樹中不存在度為1的結點。
三、哈夫曼樹的C語言實現
代碼如下:
#include<stdio.h>
#include<stdlib.h>
/*哈夫曼樹結點*/
typedef struct HTNode
{
int weight;//結點權值
int lchild, rchild, parent;//左右孩子,雙親
}HTNode,*HuffmanTree;
/*選取兩個結點權值最小的結點*/
void Select(HuffmanTree HT, int n, int& post1, int& post2)
{
int minum = 0;//保存最小值
/*尋找第一個最小值*/
for (int i = 1; i <= n; i++)
{
if (HT[i].parent == 0)//該結點沒有雙親
{
minum = i;
break;
}
}
for (int i = 1; i <= n; i++)
{
if (HT[i].parent == 0)
{
if (HT[i].weight < HT[minum].weight)//比較
{
minum = i;
}
}
}
post1 = minum;
/*尋找第二個最小值*/
for (int i = 1; i <= n; i++)
{
if (HT[i].parent == 0 && i != post1)//該結點沒有雙親,且不與第一個最小值結點相同
{
minum = i;
break;
}
}
for (int i = 1; i <= n; i++)
{
if (HT[i].parent == 0 && i != post1)
{
if (HT[i].weight < HT[minum].weight)//比較
{
minum = i;
}
}
}
post2 = minum;
}
/*創建哈夫曼樹*/
void CreatHuffTree(HuffmanTree& HT, int n, int* weight)
{
int m, s1, s2;
m = 2 * n - 1;//m表示哈夫曼樹的總結點個數
HT = (HuffmanTree)malloc(sizeof(HTNode) * (m + 1));//申請m+1個結點空間,因為第一個不使用
/*1~n表示葉子結點,即存放字符的結點,初始化*/
for (int i = 1; i <= n; i++)
{
HT[i].parent = 0;
HT[i].weight = weight[i];
HT[i].lchild = 0;
HT[i].rchild = 0;
}
/*n+1~m為分支結點,初始化*/
for (int i = n + 1; i <= m; i++)
{
HT[i].parent = 0;
HT[i].weight = 0;
HT[i].lchild = 0;
HT[i].rchild = 0;
}
printf("哈夫曼樹如下所示:\n");
/*創建分支結點,構建哈夫曼樹*/
for (int i = n+1; i <= m; i++)
{
Select(HT, i - 1, s1, s2);
/*注意,以下兩行代碼使得s1,s2兩個結點的雙親為結點i,並且不會再被Select函數選擇,相當於刪除了兩結點*/
HT[s1].parent = i;
HT[s2].parent = i;
HT[i].lchild = s1;
HT[i].rchild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;//構造出來的結點的權值等於其左右子樹的根結點權值的和
printf("%d\t%d\t%d\n", HT[i].weight, HT[s1].weight, HT[s2].weight);
}
}
int main()
{
HuffmanTree HT;
int n;//n表示初始結點個數
int* weight;
printf("請輸入初始結點個數:");
scanf("%d", &n);
weight = (int*)malloc(sizeof(int) * (n + 1));
for (int i = 1; i <= n; i++)
{
printf("請輸入第%d個結點的權值:\n", i);
scanf("%d", &weight[i]);
}
CreatHuffTree(HT, n, weight);
return 0;
}
四、哈夫曼編碼
采用前綴編碼。
前綴編碼:沒有一個編碼是另一個編碼的前綴。
從哈夫曼樹得到哈夫曼編碼是一件很自然的事情。首先,將每個出現的字符當做一個獨立的結點,其權值為它出現的頻度(次數),構造出相應的哈夫曼樹。這樣,所有的字符結點都出現在葉子結點中。我們可以將字符的編碼解釋為從根結點到該字符的路徑上邊標記的序列,其中邊標記為0表示“轉向左孩子”,標記為1表示“轉向右孩子”。