[toc]
前言
哈夫曼數
是而二叉樹的一種特殊
形式,又稱為最優二叉樹
,主要用于數據解壓
和編碼長度
的優化.
重要概念
- 路徑和路徑長度
在一棵樹種,從一個結點往下可以到達孩子或孩子的孩子結點直接的通路,成為路徑.通路分支的數目成為路徑長度.
如果規定,根節點的層數為1
,那么到達L層
路徑的結點路徑長度為L-1
.
- 結點的權及帶權路徑長度
若將樹中結點
賦給一個有著某種含義
的數值
,則這個數值
稱為該結點的權
。結點的帶權路徑長度為:從根結點
到該結點
之間的路徑長度
與該結點
的權
的乘積
.
- 樹的帶權路徑長度
樹的帶權
路徑長度規定為所有
葉子結點的帶權路徑
長度之和
,記為WPL
。
圖二叉樹WPL為:
WPL = 5*2+10*2+15*1 = 45
哈夫曼樹
定義
給定n
個權值作為n
個葉子結點,構造一棵二叉樹
,若帶權路徑長度達到最小
,稱這樣的二叉樹為最優二叉樹
,也稱為哈夫曼樹(Huffman Tree
).
哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近.
葉子結點為A、B、C、D
,對應權值分別為7、5、2、4
。
左邊樹的WPL
= 7 * 2 + 5 * 2 + 2 * 2 + 4 * 2 = 36
右邊樹的WP
L =7 * 1 + 5 * 2 + 2 * 3 + 4 * 3 = 35
由ABCD
構成葉子結點的二叉樹形態有許多種,但是WPL
最小的樹只有左邊樹所示的形態。則左邊樹霍夫曼樹
。
構造哈夫曼樹
構造哈夫曼樹主要運用于編碼
,稱為哈夫曼編碼
.
構造哈夫曼樹的算法如下:
1)對給定的n
個權值{W1,W2,W3,...,Wi,...,Wn
}構成n棵
二叉樹的初始集合F={T1,T2,T3,...,Ti,..., Tn}
,其中每棵二叉樹Ti
中只有一個權值為Wi
的根結點,它的左右子樹均為空
。
2)在F
中選取兩棵根結點權值最小的樹作為新構造的二叉樹的左右子樹
,新二叉樹的根結點的權值為其左右子樹
的根結點的權值之和
。
3)從F
中刪除這兩棵樹,并把這棵新的二叉樹同樣以升序排列加入到集合F
中。
4)重復2
)和3
),直到集合F中只有一棵二叉樹為止
。
-
第一步,對結點進行排序
d59f745a2b88b5642087170045902c61 -
第二步,將最小的5和8構造傳一棵子樹
f79a4ebba755b8b7bd3aa592462276ee -
第三步,5+8等于13,小于15,將13,15構造成一棵子樹
28e599a215e8ac6919402f24d843f1c4 -
第四步,由于13+15>15和27,所以將15,27構造成一棵子樹
e5291d83746a315b5a4245036fa8eeb5 -
第五步,30是大于28,30和28構造一棵子樹,
608fb9885a18a1c0cab82c47516ba66a -
最后連起來就是:
2d0b1ee8b583a77b29c4bf6e23f02dc7
- 哈夫曼編碼
編碼規則:從根節點
出發,向左標記為0
,向右標記為1
.
代碼實現
-
哈夫曼樹
8aeb99b546424a41033939ab8c4abfdf -
哈夫曼編碼
24a384a87746fac18b989214c0d9c296 構造結構體
const int MaxValue = `10000`;//初始設定的權值最大值
const int MaxBit = `4`;//初始設定的最大編碼位數
const int MaxN = `10`;//初始設定的最大結點個數
typedef struct HaffNode{
int weight;
int flag;
int parent;
int leftChild;
int rightChild;
}HaffNode;
typedef struct Code//存放哈夫曼編碼的數據元素結構
{
int bit[MaxBit];//數組
int start; //編碼的起始下標
int weight;//字符的權值
}Code;
- 哈夫曼樹
void Haffman(int weight[],int n,HaffNode *haffTree){
int j,m1,m2,x1,x2;
//1.哈夫曼樹初始化
//n個葉子結點. 2n-1
for(int i = 0; i < 2*n-1;i++){
if(I<n)
haffTree[i].weight = weight[I];
else
haffTree[i].weight = 0;
haffTree[i].parent = 0;
haffTree[i].flag = 0;
haffTree[i].leftChild = -1;
haffTree[i].rightChild = -1;
}
//2.構造哈夫曼樹haffTree的n-1個非葉結點
for (int i = 0; i< n - 1; i++){
m1 = m2 = MaxValue;
x1 = x2 = 0;
//2,4,5,7
for (j = 0; j< n + i; j++)//循環找出所有權重中,最小的二個值--morgan
{
if (haffTree[j].weight < m1 && haffTree[j].flag == 0)
{
m2 = m1;
x2 = x1;
m1 = haffTree[j].weight;
x1 = j;
} else if(haffTree[j].weight<m2 && haffTree[j].flag == 0)
{
m2 = haffTree[j].weight;
x2 = j;
}
}
//3.將找出的兩棵權值最小的子樹合并為一棵子樹
haffTree[x1].parent = n + I;
haffTree[x2].parent = n + I;
//將2個結點的flag 標記為1,表示已經加入到哈夫曼樹中
haffTree[x1].flag = 1;
haffTree[x2].flag = 1;
//修改n+i結點的權值
haffTree[n + i].weight = haffTree[x1].weight + haffTree[x2].weight;
//修改n+i的左右孩子的值
haffTree[n + i].leftChild = x1;
haffTree[n + i].rightChild = x2;
}
}
- 哈夫曼編碼
void HaffmanCode(HaffNode haffTree[], int n, Code haffCode[])
{
//1.創建一個結點cd
Code *cd = (Code * )malloc(sizeof(Code));
int child, parent;
//2.求n個葉結點的哈夫曼編碼
for (int i = 0; i<n; I++)
{
//從0開始計數
cd->start = 0;
//取得編碼對應權值的字符
cd->weight = haffTree[i].weight;
//當葉子結點i 為孩子結點.
child = I;
//找到child 的雙親結點;
parent = haffTree[child].parent;
//由葉結點向上直到根結點
while (parent != 0)
{
if (haffTree[parent].leftChild == child)
cd->bit[cd->start] = 0;//左孩子結點編碼0
else
cd->bit[cd->start] = 1;//右孩子結點編碼1
//編碼自增
cd->start++;
//當前雙親結點成為孩子結點
child = parent;
//找到雙親結點
parent = haffTree[child].parent;
}
int temp = 0;
for (int j = cd->start - 1; j >= 0; j--){
temp = cd->start-j-1;
haffCode[i].bit[temp] = cd->bit[j];
}
//把cd中的數據賦值到haffCode[I]中.
//保存好haffCode 的起始位以及權值;
haffCode[i].start = cd->start;
//保存編碼對應的權值
haffCode[i].weight = cd->weight;
}
}