1. 哈夫曼樹的構造
給定N個權值分別為w1, w2, ..., Wn的節點。構造哈夫曼樹的算法描述如下:
1)將這N個結點分別作為N棵樹僅含一個結點的二叉樹,構成森林F.
2)構造一個新節點,並從F中選取兩棵根結點權值最小的樹作為新節點的左、右子樹,並且將新節點的權值置為左、右子樹上根結點的權
值之和。
3)從F中刪除剛才選出的兩棵樹,同時將新得到的樹加入F中。
4)重復步驟2和3,直至F中只剩下一棵樹為止。
2. 哈夫曼編碼
哈夫曼編碼是一種被廣泛應用而且非常有效的數據壓縮編碼,它是可變長度編碼。可變長編碼即可以對待處理字符串中不同字符使用不等長的二進制位表示,可變長編碼比固定長度編碼好很多,可以對頻率高的字符賦予短編碼,而對頻率較低的字符則賦予較長一些的編碼,從而可以使平均編碼長度減短,起到壓縮數據的效果。
哈夫曼編碼是前綴編碼。如果沒有一個編碼是另一個編碼的前綴,則稱這樣的編碼為前綴編碼。對前綴編碼的解碼也是相對簡單的,因為沒有一個碼是另一個編碼的前綴,所以可以識別出第一個編碼,將它翻譯為原碼,再對余下的編碼文件重復同樣操作。
哈夫曼編碼首先要構造一棵哈夫曼樹,首先,將每個出現的字符當做一個獨立的結點,其權值作為它出現的頻度(或次數),然后構造哈夫曼樹。顯然所有字符節點均出現在葉子結點中。我們可以將字符的編碼解釋為從根至該字符路徑上標記的序列,其中標記為0表示"轉向左孩子",標記為1表示為"轉向右孩子"。 如下圖,矩形方塊表示字符及出現的次數
因此,這棵哈夫曼樹的WPL為:
WPL = 1*45 + 3 * (13+12+16) + 4 * (5+9) = 224
#ifndef _HAFFMANTREE_H #define _HAFFMANTREE_H /* 實現對赫夫曼編碼主要步驟: 1 生成一個節點數組用來存放樹的節點信息 2 初始化 3 從中找出權值最小和權值次小節點然后生成這兩個節點的雙親節點權值最小節點是該雙親節點的左孩子次小為右孩子然后將該雙親節點加入到節點數組中並標記為已加入 4 重復三步驟知道赫夫曼樹構造成功 */ #include<stdio.h> #include<malloc.h> #define LEN_CODE sizeof(HaffmanCode) #define LEN_NODE sizeof(haffmannode) #define MAXWEIGHT 1000 //用來存儲最大的權值數 #define MaxBits 30 //用來存儲轉化后的編碼 typedef struct HaffManTree { int weight; //存放節點的權值 int LeftChild,RightChild,Parent; //采用仿真指針方法存放節點的左右孩子和雙親下標 int flag; //用來判斷該節點是否已經加入了赫夫曼樹中( 0表示未加入,1表示已經加入 ) }haffmannode; typedef struct HaffMancode { int bits[MaxBits]; //用來存放編碼 int weight; //用來存放對應的權值 int start; //存放編碼的起始位置 }HaffmanCode; void MakeTree(int weight[],int n,haffmannode haffmantree[]); void MakeCode(haffmannode haffmantree[],int n,HaffmanCode haffmancode[]); void DispCode(char temp[],haffmannode tree[],HaffmanCode Code[],int n); #endif
/* 功能函數的實現 */ #include"haffman.h" void MakeTree(int weight[],int n,haffmannode haffmantree[]) { int i,j,m1,m2,x1,x2; //初始化(n個葉節點一共 2*n-1 個節點) for(i=0;i < 2*n-1; i++) { if(i < n) //處理 n 個葉子節點 { haffmantree[i].weight=weight[i]; } else haffmantree[i].weight=0; haffmantree[i].LeftChild=-1; //開始時候讓左右孩子和雙親的下標為 -1 haffmantree[i].RightChild=-1; haffmantree[i].Parent=-1; haffmantree[i].flag=0; } //每次從中選出權值最小的和次小的構成樹的左右子樹 //處理 n-1 個非葉子節點 for(i=0; i < n-1;i++) { m1=m2=MAXWEIGHT; x1=x2=0; //從有權值的節點中查找 m1 用來存放權值最小的 m2 用來存放權值次小的 x1 用來存放權值最小的下標 x2 用來存放權值次小的下標 for(j=0;j < n+i; j++) { if(haffmantree[j].weight < m1 && haffmantree[j].flag==0) { m2=m1; x2=x1; m1=haffmantree[j].weight; x1=j; } else if(haffmantree[j].weight < m2 && haffmantree[j].flag==0) { m2=haffmantree[j].weight; x2=j; } } haffmantree[x1].Parent=n+i; // 比如第一個雙親節點的下標就是 n 第二個雙親節點的下標就是 n+1 -------- haffmantree[x2].Parent=n+i; haffmantree[x1].flag=1; haffmantree[x2].flag=1; haffmantree[n+i].weight=haffmantree[x1].weight + haffmantree[x2].weight; //權值求和 //printf("%d\n",haffmantree[n+i].weight); haffmantree[n+i].LeftChild=x1; haffmantree[n+i].RightChild=x2; } } //本函數用來實現對赫夫曼編碼的解決 void MakeCode(haffmannode haffmantree[],int n,HaffmanCode haffmancode[]) { int i,j; int Child,Parent; HaffmanCode *code; code=(HaffmanCode *)malloc(LEN_CODE); //用來存放臨時數據 for(i=0;i < n ;i++) { code->weight=haffmantree[i].weight; code->start=0; Child=i; Parent=haffmantree[Child].Parent; while(Parent != -1) //當雙親節點不為根節點時候 { if(haffmantree[Parent].LeftChild==Child) //如果當前節點是雙親節點的左孩子就將0加入到該編碼中如果是右孩子就將1加入到編碼中 code->bits[code->start]=0; else code->bits[code->start]=1; code->start++; Child=Parent; //自底向上移動判斷 Parent=haffmantree[Child].Parent; } for(j=0;j < code->start;j++) //把該編碼加入到對應的編碼數組中 { haffmancode[i].bits[j]=code->bits[j]; } haffmancode[i].weight=code->weight; //把對應的權值加入到對應編碼數組的權值中去 haffmancode[i].start=code->start; //將結束位置復制進去 } } void DispCode(char temp[],haffmannode tree[],HaffmanCode Code[],int n) //本函數主要用來實現哈夫曼編碼的輸出 { int i,j; printf("\n輸出赫夫曼編碼:\n\n"); for(i=0;i<n;i++) { printf("字符= %c \tWeight= %d \t\n字符= %c \tCode=",temp[i],tree[i].weight, temp[i]); for(j=(Code[i].start-1);j>=0;j--)//輸出編碼 { printf("%d",Code[i].bits[j]); } printf("\n\n"); } }
