概念:
哈夫曼(Huffman)樹又稱最優二叉樹或最優搜索樹,是一種帶權路徑長度最短的二叉樹。在許多應用中,常常賦給樹中結點一個有某種意義的實數,稱此實數爲該結點的權。從樹根結點到該結點之間的路徑長度與該結點上權的乘積稱爲結點的帶權路徑長度(WPL),樹中所有葉子結點的帶權路徑長度之和稱爲該樹的帶權路徑長度.
算法思想:
(1) 以權值分別爲W1,W2...Wn的n各結點,構成n棵二叉樹T1,T2,...Tn並組成森林F={T1,T2,...Tn},其中每棵二叉樹 Ti僅有一個權值爲 Wi的根結點;
(2) 在F中選取兩棵根結點權值最小的樹作爲左右子樹構造一棵新二叉樹,並且置新二叉樹根結點權值爲左右子樹上根結點的權值之和(根結點的權值=左右孩子權值之和,葉結點的權值= Wi)
(3) 從F中刪除這兩棵二叉樹,同時將新二叉樹加入到F中;
(4) 重複(2)、(3)直到F中只含一棵二叉樹爲止,這棵二叉樹就是Huffman樹。
#include <iostream>
#include <cstdio>
using namespace std;
int n, m;
typedef struct
{
int weight; //節點權重
int parent, lchild, rchild; //節點的雙親下標,左右孩子的下標
int code; //節點的編號
}htnode, *huffmantree;
// 選擇兩個權值最小的節點
void Selectmin(huffmantree ht, int n, int &s1, int &s2)
{
s1 = s2 = 0; // 初始化兩最小個節點的位置
int i;
for(i=1; i<n; i++) // 從第一個節點到新添節點找兩個無雙親節點
{
if( 0 == ht[i].parent)
{
if( 0 == s1 )
s1 = i;
else
{
s2 = i;
break;
}
}
}
if(ht[s1].weight > ht[s2].weight) // 判斷兩節點的大小,永遠保持s1的權值比s2小
{
int t = s1;
s1 = s2;
s2 = t;
}
for(i+=1; i<n; i++)
{
if( 0 == ht[i].parent )
{
if(ht[i].weight < ht[s1].weight )
{
s2 = s1;
s1 = i;
}
else if( ht[i].weight < ht[s2].weight )
s2 = i;
}
}
}
// 創建huffman樹
void creathuffmantree(huffmantree &ht)
{
cin >> n; // 輸入要輸入的節點數
m = 2*n - 1; // n個葉子的huffman樹有2n-1個節點
ht = new htnode[m + 1]; // htnode[0] 不存節點
for( int i=1; i<=m; i++) // 初始化每個節點的值
{
ht[i].parent = ht[i].lchild = ht[i].rchild = 0;
ht[i].code = -1; // 初始化編碼都爲-1
}
for( int i=1; i<=n; i++) // 輸入每個節點的權值
{
cin >> ht[i].weight;
}
ht[0].weight = m; // ht[0]的權值設爲節點個數
for( int i=n+1; i<=m; i++) // 有n個節點,從n+1的位置開始添加節點
{
int s1,s2;
Selectmin(ht, i, s1, s2); // 傳入要添加節點的huffman樹和要添加的位置
// 返回兩個權值最小的位置,並且s1<=s2
ht[s1].parent = i; // 將兩節點的雙親設爲新節點的位置
ht[s2].parent = i;
ht[s1].code = 0; // 較小的節點編碼爲0
ht[s2].code = 1; // 較大的節點編碼爲1
ht[i].lchild = s1; // 添加新節點,左右孩子爲兩節點的位置
ht[i].rchild = s2;
ht[i].weight = ht[s1].weight + ht[s2].weight; // 新節點的權值爲左右孩子權值的和
}
}
// 計算huffman樹的總路徑長度
int huffmantreeWPL(huffmantree ht, int i, int d) // 傳入huffman樹,節點位置,節點深度
{
if(ht[i].lchild == 0 && ht[i].rchild == 0 ) // 若該節點爲葉子節點,算出該節點的路徑
return ht[i].weight * d;
else // 若該節點不是葉子節點,則該節點的路徑等於左右孩子的路徑和
return huffmantreeWPL(ht, ht[i].lchild, d+1) + huffmantreeWPL(ht, ht[i].rchild, d+1);
}
// 輸出huffman樹各節點的信息
void printf(huffmantree ht)
{
cout << "index weight parent lchild rchild" << endl;
for(int i=1; i<=m; i++)
{
cout << " " << i << " " << " ";
cout << " " << ht[i].weight << " ";
cout << " " << ht[i].parent << " ";
cout << " " << ht[i].lchild << " ";
cout << " " << ht[i].rchild << " " << endl;
}
}
// 輸出各葉子節點的編碼
void Encoding(huffmantree ht, int i)
{
if(ht[i].parent==0) // 若該節點爲根節點返回上一層
return;
else
{
Encoding(ht, ht[i].parent); // 用遞歸找到該葉子的根節點,輸入從根節點到葉子的編碼
}
cout << ht[i].code;
}
// 輸出葉子節點的編碼
void huffmantreeEncoding(huffmantree ht)
{
for(int i=1; i<=n; i++) // 只輸出前n個節點的編碼
{
if(ht[i].lchild==0 && ht[i].rchild==0) // 如果該節點爲葉子節點
{
printf("%d:",ht[i].weight);
Encoding(ht,i); // 用遞歸輸出該節點的編碼
printf("\n");
}
}
}
int main()
{
huffmantree ht;
creathuffmantree(ht); // 先創建一個huffman樹
printf(ht); // 輸出huffman樹各節點的信息
cout << "WPL = " << huffmantreeWPL(ht,m,0) << endl; // 算出huffman樹的總路徑長度
huffmantreeEncoding(ht); // 輸出各葉子節點的編碼
return 0;
}