huffman編碼解碼與huffman樹
阿新 • • 發佈:2019-02-18
定義:給定n個權值作為n個葉子結點,構造一棵二叉樹,若帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。
構建huffman樹:
- 1.根據給定的n個權值{w1,w2,…,wn}構成二叉樹集合F={T1,T2,…,Tn},其中每棵二叉樹Ti中只有一個帶權為wi的根結點,其左右子樹為空.
- 2.在F中選取兩棵根結點權值最小的樹作為左右子樹構造一棵新的二叉樹,且置新的二叉樹的根結點的權值為左右子樹根結點的權值之和.
- 3.在F中刪除這兩棵樹,同時將新的二叉樹加入F中.
- 4.重複2、3,直到F只含有一棵樹為止.(得到哈夫曼樹)
關於huffman樹有以下幾點要注意的:
- 1、滿二叉樹不一定是哈夫曼樹
- 2、哈夫曼樹中權越大的葉子離根越近 (很好理解,WPL最小的二叉樹)
- 3、具有相同帶權結點的哈夫曼樹不惟一
- 4、哈夫曼樹的結點的度數為 0 或 2, 沒有度為 1 的結點。
- 5、包含 n 個葉子結點的哈夫曼樹中共有 2n – 1 個結點。
- 6、包含 n 棵樹的森林要經過 n–1 次合併才能形成哈夫曼樹,共產生 n–1 個新結點
根據huffman樹得到字元編碼:
從huffman樹的根節點開始,向左為0,向右為1,直到遇見野子節點,記錄下沿途的編碼即可
解碼/譯碼:
對於給定的二進位制碼串,想要解碼出字元:從哈夫曼樹根開始,對待譯碼電文逐位取碼。若編碼是“0”,則向左走;若編碼是“1”,則向右走,一旦到達葉子結點,則譯出一個字元;再重新從根出發,直到電文結束。
下面附一份c語言版的huffman編碼實現:
//haffman 樹的結構
typedef struct
{
//葉子結點權值
unsigned int weight;
//指向雙親,和孩子結點的指標
unsigned int parent;
unsigned int lChild;
unsigned int rChild;
} Node, *HuffmanTree;
//動態分配陣列,儲存哈夫曼編碼
typedef char *HuffmanCode;
//選擇兩個parent為0,且weight最小的結點s1和s2的方法實現
//n 為葉子結點的總數,s1和 s2兩個指標引數指向要選取出來的兩個權值最小的結點
void select(HuffmanTree *huffmanTree, int n, int *s1, int *s2)
{
//標記 i
int i = 0;
//記錄最小權值
int min;
//遍歷全部結點,找出單節點
for(i = 1; i <= n; i++)
{
//如果此結點的父親沒有,那麼把結點號賦值給 min,跳出迴圈
if((*huffmanTree)[i].parent == 0)
{
min = i;
break;
}
}
//繼續遍歷全部結點,找出權值最小的單節點
for(i = 1; i <= n; i++)
{
//如果此結點的父親為空,則進入 if
if((*huffmanTree)[i].parent == 0)
{
//如果此結點的權值比 min 結點的權值小,那麼更新 min 結點,否則就是最開始的 min
if((*huffmanTree)[i].weight < (*huffmanTree)[min].weight)
{
min = i;
}
}
}
//找到了最小權值的結點,s1指向
*s1 = min;
//遍歷全部結點
for(i = 1; i <= n; i++)
{
//找出下一個單節點,且沒有被 s1指向,那麼i 賦值給 min,跳出迴圈
if((*huffmanTree)[i].parent == 0 && i != (*s1))
{
min = i;
break;
}
}
//繼續遍歷全部結點,找到權值最小的那一個
for(i = 1; i <= n; i++)
{
if((*huffmanTree)[i].parent == 0 && i != (*s1))
{
//如果此結點的權值比 min 結點的權值小,那麼更新 min 結點,否則就是最開始的 min
if((*huffmanTree)[i].weight < (*huffmanTree)[min].weight)
{
min = i;
}
}
}
//s2指標指向第二個權值最小的葉子結點
*s2 = min;
}
//建立哈夫曼樹並求哈夫曼編碼的演算法如下,w陣列存放已知的n個權值
void createHuffmanTree(HuffmanTree *huffmanTree, int w[], int n)
{
//m 為哈夫曼樹總共的結點數,n 為葉子結點數
int m = 2 * n - 1;
//s1 和 s2 為兩個當前結點裡,要選取的最小權值的結點
int s1;
int s2;
//標記
int i;
// 建立哈夫曼樹的結點所需的空間,m+1,代表其中包含一個頭結點
*huffmanTree = (HuffmanTree)malloc((m + 1) * sizeof(Node));
//1--n號存放葉子結點,初始化葉子結點,結構陣列來初始化每個葉子結點,初始的時候看做一個個單個結點的二叉樹
for(i = 1; i <= n; i++)
{
//其中葉子結點的權值是 w【n】陣列來儲存
(*huffmanTree)[i].weight = w[i];
//初始化葉子結點(單個結點二叉樹)的孩子和雙親,單個結點,也就是沒有孩子和雙親,==0
(*huffmanTree)[i].lChild = 0;
(*huffmanTree)[i].parent = 0;
(*huffmanTree)[i].rChild = 0;
}// end of for
//非葉子結點的初始化
for(i = n + 1; i <= m; i++)
{
(*huffmanTree)[i].weight = 0;
(*huffmanTree)[i].lChild = 0;
(*huffmanTree)[i].parent = 0;
(*huffmanTree)[i].rChild = 0;
}
printf("\n HuffmanTree: \n");
//建立非葉子結點,建哈夫曼樹
for(i = n + 1; i <= m; i++)
{
//在(*huffmanTree)[1]~(*huffmanTree)[i-1]的範圍內選擇兩個parent為0
//且weight最小的結點,其序號分別賦值給s1、s2
select(huffmanTree, i-1, &s1, &s2);
//選出的兩個權值最小的葉子結點,組成一個新的二叉樹,根為 i 結點
(*huffmanTree)[s1].parent = i;
(*huffmanTree)[s2].parent = i;
(*huffmanTree)[i].lChild = s1;
(*huffmanTree)[i].rChild = s2;
//新的結點 i 的權值
(*huffmanTree)[i].weight = (*huffmanTree)[s1].weight + (*huffmanTree)[s2].weight;
printf("%d (%d, %d)\n", (*huffmanTree)[i].weight, (*huffmanTree)[s1].weight, (*huffmanTree)[s2].weight);
}
printf("\n");
}
//哈夫曼樹建立完畢,從 n 個葉子結點到根,逆向求每個葉子結點對應的哈夫曼編碼
void creatHuffmanCode(HuffmanTree *huffmanTree, HuffmanCode *huffmanCode, int n)
{
//指示biaoji
int i;
//編碼的起始指標
int start;
//指向當前結點的父節點
int p;
//遍歷 n 個葉子結點的指示標記 c
unsigned int c;
//分配n個編碼的頭指標
huffmanCode=(HuffmanCode *)malloc((n+1) * sizeof(char *));
//分配求當前編碼的工作空間
char *cd = (char *)malloc(n * sizeof(char));
//從右向左逐位存放編碼,首先存放編碼結束符
cd[n-1] = '\0';
//求n個葉子結點對應的哈夫曼編碼
for(i = 1; i <= n; i++)
{
//初始化編碼起始指標
start = n - 1;
//從葉子到根結點求編碼
for(c = i, p = (*huffmanTree)[i].parent; p != 0; c = p, p = (*huffmanTree)[p].parent)
{
if( (*huffmanTree)[p].lChild == c)
{
//從右到左的順序編碼入陣列內
cd[--start] = '0'; //左分支標0
}
else
{
cd[--start] = '1'; //右分支標1
}
}// end of for
//為第i個編碼分配空間
huffmanCode[i] = (char *)malloc((n - start) * sizeof(char));
strcpy(huffmanCode[i], &cd[start]);
}
free(cd);
//列印編碼序列
for(i = 1; i <= n; i++)
{
printf("HuffmanCode of %3d is %s\n", (*huffmanTree)[i].weight, huffmanCode[i]);
}
printf("\n");
}
int main(void)
{
HuffmanTree HT;
HuffmanCode HC;
int *w,i,n,wei,m;
printf("\nn = " );
scanf("%d",&n);
w=(int *)malloc((n+1)*sizeof(int));
printf("\ninput the %d element's weight:\n",n);
for(i=1; i<=n; i++)
{
printf("%d: ",i);
fflush(stdin);
scanf("%d",&wei);
w[i]=wei;
}
createHuffmanTree(&HT, w, n);
creatHuffmanCode(&HT,&HC,n);
return 0;
}
由於哈夫曼樹中沒有度為1的結點,則一棵有n個葉子的哈夫曼樹共有2×n-1個結點,所以用一個大小為2×n-1 的一維陣列存放哈夫曼樹的各個結點。 由於每個結點同時還包含其雙親資訊和孩子結點的資訊,所以構成一個靜態三叉連結串列。