哈夫曼樹(資料結構)
設二叉樹具有n個帶權值的葉子節點,從根節點到葉子節點的路徑長度與對應葉子節點權值的乘積之和叫做二叉樹的“帶權路徑長度”。
對於一組帶有權值的葉子節點,帶權路徑長度最小的二叉樹叫做“最優二叉樹”(例如哈夫曼樹,哈夫曼樹是最優二叉樹,最優二叉樹不一定是哈夫曼樹)。
如何建立一顆哈夫曼樹?
建立n個根節點,權值為{w1,w2,,,,wn},帶到森林{T1,T2,,,,Tn};從森林中選取權值最小的兩顆二叉樹,合併為新的二叉樹,新二叉樹根節點的權值為兩權值之和;將新二叉樹加入森林,被歸併的兩顆二叉樹不再看做是二叉樹;重複選取合併操作,直至森林只有一顆二叉樹,得到的二叉樹就是哈夫曼樹。哈夫曼樹不一定唯一,但最小帶權路徑長度都相同。
只要權值個數(葉節點個數)嚴格大於1,哈夫曼樹中便不存在度為1的節點,權值個數 (葉節點個數)為n,則哈夫曼樹的節點個數為(2n-1)。
哈夫曼樹對應的編碼為哈夫曼編碼,是一種最優字首編碼。
7.建立哈夫曼樹的思路:
分析:含n個字元則哈夫曼樹有(2n-1)個節點,動態開闢(2n-1)個節點的記憶體,用順序儲存結構儲存。構造樹時,從葉子節點往上走,識別字符或者解碼時從上往下走,故節點要包含雙親,左右孩子下標和權值。
具體思路:
1)動態開闢所有節點的儲存空間,初始化各葉節點和分支節點(已知各葉節點的權值,其他各項初始化為0,具體原因下面分析。)
2)構造哈夫曼樹關鍵是逐步確定各分支節點的相關資訊:求出最小的二叉樹,據此設定當前各分支節點各成員的值。
3)開闢空指標陣列。開闢臨時存放單個編碼的陣列,從葉出發逆向尋根,每向上一步都將當前編碼符記錄到臨時陣列最後一個空位置,待到達根則臨時陣列中得到臨時編碼,後將該編碼複製到指標陣列的適當位置即可。
構建哈夫曼樹,求哈夫曼編碼程式碼:
typedef struct
{
unsigned int weight;
unsigned int parent,lchild,rchild;
} HTNode;
typedef HTNode *HuffmanTree;
typedef char* *HuffmanCode;//用於存放編碼的陣列
int minn(HuffmanTree &HT,int k)
{
int i = 0;
int min_weight = 0;
int min_index= 0;
//HT[i]->parent != 0說明該節點不能再用來做其他節點的孩子
while(HT[i].parent != 0)
{
++i;
}
//記下最小的權值及其對應於HT的下標
min_weight = HT[i].weight;
min_index = i;
//選出weight最小的元素後,將其parent置為-1,使得下一次比較選取時將其排除在外
HT[i].parent = -1;
return min_weight;
}
Status Select(HuffmanTree &HT,int k,int &min1,int &min2)
{
min1 = minn(HT,k);//從未用過的前k個數裡選出一個最小的數
min2 = minn(HT,k);//從未用過的前k個數裡選出一個最小的數(在minn的具體實現裡會控制不再選取第一次選出的數)
return true;
}
//w為權值陣列,HT為要建立的哈夫曼樹,HC為存放單個編碼的陣列,n為葉子節點個數
Status HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int *w,int n)
{
if(n <= 1)
return ERROR;
int m = 2*n-1;//整棵哈夫曼樹的節點個數
HT = (HTNode*)malloc((m+1)*sizeof(HTNode));//分配空間的時候多分配一個,從第一個開始存,0號不存
int i = 1;
//先初始化葉子,只知道權值
for(i = 1; i <= n; ++i,++w)
{
HT[i].weight = *w;
HT[i].parent = 0;
HT[i].lchild = 0;
HT[i].rchild = 0;
}
//初始化分支節點,各個資訊賦值為0
for(i = n+1; i <= m; ++i,++p)
{
HT[i].weight = 0;
HT[i].lchild = 0;
HT[i].rchild = 0;
HT[i].parent = 0;
}
//哈夫曼樹共有2n-1個節點,前n個節點是葉子節點,已經有資訊了,剩下的節點從第n+1個開始放
for(i = n+1; i <= m; ++i)
{
Select(HT,i-1,min1,min2);
HT[min1].parent = i;
HT[min2].parent = i;
HT[i].weight = min1 + min2;
HT[i].lchild = min1;
HT[i].rchild = min2;
HT[i].parent = 0;
}
//共有n個節點,每個節點對應一個字首編碼
HC = (HuffmanCode*)malloc((n+1)*sizeof(char*));
char * cd = (char*)malloc(sizeof(char)*n);//存編碼的臨時空間,編碼最長為(n-1)
cd[n-1] = '\0';
//逐個葉節點求其編碼(HT中1-n是葉子節點)
for(int i = 1; i <= n; ++i)
{
int start = n-1;
int c;
unsigned int f;
//葉節點逆向求編碼
for(f=HT[i].parent,c=i;f != 0; c = f,f = HT[f].parent)
{
if(HT[f].lchild == c)
cd[--start] = '0';
else
cd[--start] = '1';
}
//上一個編碼用了n-start個空間,再分配n-start個空間
HC[i] = (char*)malloc(sizeof(char)*(n-start));
strcpy(HC[i],&cd[start]);
}
}
(Select函式!!!minn函式!!!構建哈夫曼樹的思路!!!)