哈夫曼樹的創建和編碼
項目忙的要死,博客停了兩天,做外包的真不好受,還是做產品的強。軟件最後最值錢的不是代碼,而是相關的文檔,文檔清楚,依葫蘆畫瓢照做出來應該不難。項目結束了至少要整理出需求規格說明書,系統設計文檔,用戶使用說明書,開發進度表,投標書,工作說明書等文檔。
本文根據《數據結構與算法》(C語言版)(第三版) 整理。
1.哈夫曼樹又稱最優二叉樹,是一類帶權路徑長度最短的樹。
對於最優二叉樹,權值越大的結點越接近樹的根結點,權值越小的結點越遠離樹的根結點。最優二叉樹的構造算法步驟:
(1)根據給定的n個權值w1,w2,...,wn構成n棵二叉樹森林F={T1,T2,...,Tn},其中每一棵二叉樹Ti中都只有一個權爲wi的根結點,其左、右子樹爲空。
(2)在森林F中選出兩棵根結點權值最小的樹作爲一棵新二叉樹的左、右子樹,新二叉樹的根結點的權值爲其左、右子樹根結點的權值之和。
(3)從F中刪除這兩棵二叉樹,同時把新二叉樹加入到F中。
(4)重複步驟(2)、(3),直到F中只含有一棵樹爲止,此樹便爲最優二叉樹。
哈夫曼樹的構造過程示意圖如下:
哈夫曼樹的結點類型聲明:
struct TreeNode
{
int weight;
int parent;
int lchild;
int rchild;
};
typedef struct TreeNode HFTreeNode;
typedef HFTreeNode HuffmanTree;
哈夫曼樹的構造算法:
#define MaxSize 1000 //葉子數目
void Select(HuffmanTree *HT, int g, int &s1, int &s2);
void CreateHuffmanTree(HuffmanTree T[MaxSize], int n)
{
int i,p1,p2;
if(n<1)
return 1;
m=2*n; //計算哈夫曼樹的結點大小
for(i=1; i<m; i++)
{
T[i].parent=0;
T[i].lchild=0;
T[i].rchild=0;
T[i].weight=0;
}
for(i=1; i<n; i++) //讀入葉子結點的權值
{
scanf("%d",&weight);
T[i].weight=weight;
}
for(i=n; i<m-1; i++)
{
SelectMin(T, i-1, p1, p2);
//在T[0...i-1]中選擇兩個權值最小的根結點,其序號分別爲p1和p2
T[p1].parent=T[p2].parent=i;
T[i].lchild=p1; //最小權的根結點是新結點的左孩子
T[i].rchild=p2; //次小權的根結點是新結點的右孩子
T[i].weight=T[p1].weight+T[p2].weight;
}
}
void selectMin(HuffmanTree *HT, int g, int &s1, int &s2)
{
int j, k, m, n;
for(k=1; k<=g; k++) //找到一個parent爲-1的子樹
{
if(HT[k].parent==0)
{
s1=k;
break;
}
}
for(j=1; j<=g; j++)
{
if((HT[j].weight<=HT[k].weight)&&(HT[j].parent==0))
//找到一個parent爲-1權值最小的子樹
s1=j;
}
for(m=1; m<=g; m++)
{
if((HT[m].parent==0)&&(m!=s1))
{
s2=m;
break;
}
}
for(n=1; n<=g; n++)
{
if((HT[n].weight<HT[m].weight)&&(HT[n].parent==0)&&(n!=s1))
s2=n;
}
}
2.哈夫曼編碼
哈夫曼編碼是一種變長編碼。其定義如下:對於給定的字符集D={d1,d2,...,dn}及其頻率分佈F={w1,w2,...,wn},用d1,d2,...,dn作爲葉結點,w1,w2,...,wn作爲結點的權,利用哈夫曼算法構造一棵最優二叉樹,將樹中每個分支結點的左分支標上"0";右分支標上"1",把從根到每個葉子的路徑符號("0"或"1")連接起來,作爲該葉子的編碼。
哈夫曼編碼是在哈夫曼樹的基礎上求出來的,其基本思想是:從葉子結點di(0<=i<n)出發,向上回溯至根結點,依次求出每個字符的編碼。
示例:對於字符集D={A,B,C,D},其頻率(單位:千次)分佈爲F={12,6,2,18},下圖給出D的哈夫曼編碼圖。
哈夫曼編碼的回溯步驟如下:
(1)選出哈夫曼樹的某一個葉子結點。
(2)利用其雙親指針parent找到其雙親結點。
(3)利用找到的雙親結點的指針域中的lchild和rchild,判斷該結點是雙親的左孩子還是右孩子。若該結點是其雙親結點的左孩子,則生成代碼0;若該結點是其雙親結點的右孩子,則生成代碼1。
(4)由於生成的編碼與要求的編碼反序,將所生成的編碼反序。
(5)重複步驟(1)~(4),直到所有結點都回溯完。
反序方法:首先將生成的編碼從後向前依次存放在一個臨時的一維數組中,並設一個指針start指示編碼在該一維數組中的起始位置。當某個葉子結點的編碼完成時,從臨時的一維數組的start處將編碼複製到該字符對應的bits中即可。
哈夫曼編碼的存儲結構: struct CodeNode
{
char ch; //存儲字符
char bits[n+1]; //存放編碼位串
};
typedef struct CodeNode CodeNoe;
typedef CodeNoe HUffmanCode[n];
哈夫曼編碼的算法:
void CharSetHuffmanEncoding(HuffmanTree T, HuffmanCode H)
{ //根據哈夫曼樹T求哈夫曼編碼表H
int c, p, i;
char cd[n+1];
int start;
cd[n]='\0';
for(i=0; i<n; i++)
{
//依次求葉子T[i]的編碼
H[i].ch=getchar(); //讀入葉子T[i]對應的字符
start=n; //編碼起始位置的初值
c=i; //從葉子T[i]開始上溯
while(p=T[c].parent>0)
{
if(T[p].lchild==c)
{
cd[--start]='0';
}
else
{
cd[--start]='1';
}
c=p; //繼續上溯
}
strcpy(H[i].bits, &cd[start]); //複製編碼位串
}
}
3. 哈夫曼解碼
哈夫曼解碼過程:從哈夫曼樹的根結點出發,依次識別電文的中的二進制編碼,如果爲0,則走向左孩子,否則走向右孩子,走到葉結點時,就可以得到相應的解碼字符。算法如下:
void CharSetHuffmanDecoding(HuffmanTree T, char* cd, int n)
{
int p=2*n-2; //從根結點開始
int i=0;
//當要解碼的字符串沒有結束時
while(cd[i]!='/0')
{
//當還沒有到達哈夫曼樹的葉子並且要解碼的字符串沒有結束時
while((T[p].lchild!=0 && T[p].rchild != 0) && cd[i] != '\0')
{
if(cd[i] == '0')
{
//如果是0,則葉子在左子樹
p=T[p].lchild;
}
else
{
//如果是1,則葉子在左子樹
p=T[p].rchild;
}
i++;
}
//如果到達哈夫曼樹的葉子時
if(T[p].lchild == 0 && T[p].rchild == 0)
{
printf("%c", T[p].ch);
p = 2*n-1;
}
else //如果編號爲p的結點不是葉子,那麼編碼有錯
{
printf("\n解碼出錯! \n");
return;
}
}
printf("\n");
}
4. 哈夫曼樹的創建和哈夫曼編碼程序:
// HuffmanTree.cpp : 定義控制檯應用程序的入口點。
#include "stdafx.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
typedef struct HuffmanTree
{
int weight;
int parent, lchild, rchild;
} HuffmanTree;
typedef struct CodeNode
{
int ch;
char bits[4+1];
}CodeNode;
void SelectMin(HuffmanTree tree[], int len, int * pos1, int* pos2)
{
int min=255;
int i, j;
*pos1=0;
*pos2=0;
for(i=0; i<len; i++)
{
if(tree[i].parent==-1)
if(min>tree[i].weight)
{
min=tree[i].weight;
*pos1=i;
}
}
min=255;
for(j=0; j<len; j++)
{
if(j==*pos1)
continue;
if(tree[j].parent==-1)
if(min>tree[j].weight)
{
min=tree[j].weight;
*pos2=j;
}
}
}
void CreateHuffmanTree(HuffmanTree tree[], int n)
{
int m=2*n;
int i;
for(i=n; i<m-1; i++)
{
int pos1, pos2;
HuffmanTree node;
SelectMin(tree, i, &pos1, &pos2);
printf("pos1=%d,pos2=%d\n", pos1, pos2);
node.weight=tree[pos1].weight+tree[pos2].weight;
tree[pos1].parent=i;
tree[pos2].parent=i;
node.lchild=pos1;
node.rchild=pos2;
node.parent=-1;
tree[i]=node;
}
}
void HuffmanEncoding(HuffmanTree tree[])
{
int c, p, i;
int start;
char cd[4+1];
cd[4]='\0';
for(i=0; i<4; i++)
{
printf("\n");
printf("%d",tree[i].weight);
printf(":");
start=4;
c=i;
while((p=tree[c].parent)!=-1)
{
if(tree[p].lchild==c)
{
cd[--start]='0';
}
else
{
cd[--start]='1';
}
c=p;
}
printf(&cd[start]);
}
}
int main(int argc, char* argv[])
{
HuffmanTree tree[4*2];
int i, j;
for(i=0; i<4; i++)
{
tree[i].lchild=-1;
tree[i].rchild=-1;
tree[i].parent=-1;
}
printf("請輸入哈夫曼樹葉子結點的權值: \n");
for(i=0; i<4; i++) //讀入葉子結點的權值
{
int weight;
scanf("%d",&weight);
tree[i].weight=weight;
}
CreateHuffmanTree(tree, 4);
for(j=0; j<2*4-1; j++)
{
printf("tree[%d]:weight=%d \n", j, tree[j].weight);
}
HuffmanEncoding(tree);
return 0;
}
Ctrl+F5執行HuffmanTree.cpp結果如下圖: