1. 程式人生 > >huffman編碼解碼與huffman樹

huffman編碼解碼與huffman樹

定義:給定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 的一維陣列存放哈夫曼樹的各個結點。 由於每個結點同時還包含其雙親資訊和孩子結點的資訊,所以構成一個靜態三叉連結串列。