利用哈夫曼樹編碼解碼
阿新 • • 發佈:2018-12-01
哈夫曼(Haffman)樹(最優樹)
定義:
給定n個權值作為n個葉子結點,構造一棵二叉樹,若該樹的帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。
構造過程:
以 1,7,3,4,9,8為例:
第一步:排序,1,3,4,7,8,9
第二步:選出最小的兩個數,1,3(哈夫曼樹是從下往上排列的),用一個樹杈連線上兩個最小的數,在頂點處計算出這兩個數字的和 並寫在上面。
第三步:然後再比較剩下的數字和這個和的大小,再取出兩個最小的數字進行排列。
第四步:如果兩個數的和正好是下一步的兩個最小數的其中的一個那麼這個樹直接往上生長就可以了;如果這兩個數的和比較大不是下一步的兩個最小數的其中一個那麼,就並列生長。
注意:(由於並未規定左右子樹,所以哈夫曼樹不唯一,同一層上的結點,位置是可以互換的)
哈夫曼樹的資料結構:
1 //haffman 樹的結構 2 typedef struct 3 { 4 //葉子結點權值 5 float weight; 6 //指向雙親,和孩子結點的指標 7 unsigned int parent; 8 unsigned int lChild; 9 unsigned int rChild; 10 } Node, *HuffmanTree; 11 12 //動態分配陣列,儲存哈夫曼編碼13 typedef char *HuffmanCode;
選擇兩個權值最小的結點:
1 //選擇兩個parent為0,且weight最小的結點s1和s2的方法實現 2 //n 為葉子結點的總數,s1和 s2兩個指標引數指向要選取出來的兩個權值最小的結點 3 void select(HuffmanTree *huffmanTree, int n, int *s1, int *s2) 4 { 5 //標記 i 6 int i = 0; 7 //記錄最小權值 8 int min; 9 //遍歷全部結點,找出單節點 10 for(i = 1; i <= n; i++)11 { 12 //如果此結點的父親沒有,那麼把結點號賦值給 min,跳出迴圈 13 if((*huffmanTree)[i].parent == 0) 14 { 15 min = i; 16 break; 17 } 18 } 19 //繼續遍歷全部結點,找出權值最小的單節點 20 for(i = 1; i <= n; i++) 21 { 22 //如果此結點的父親為空,則進入 if 23 if((*huffmanTree)[i].parent == 0) 24 { 25 //如果此結點的權值比 min 結點的權值小,那麼更新 min 結點,否則就是最開始的 min 26 if((*huffmanTree)[i].weight < (*huffmanTree)[min].weight) 27 { 28 min = i; 29 } 30 } 31 } 32 //找到了最小權值的結點,s1指向 33 *s1 = min; 34 //遍歷全部結點 35 for(i = 1; i <= n; i++) 36 { 37 //找出下一個單節點,且沒有被 s1指向,那麼i 賦值給 min,跳出迴圈 38 if((*huffmanTree)[i].parent == 0 && i != (*s1)) 39 { 40 min = i; 41 break; 42 } 43 } 44 //繼續遍歷全部結點,找到權值最小的那一個 45 for(i = 1; i <= n; i++) 46 { 47 if((*huffmanTree)[i].parent == 0 && i != (*s1)) 48 { 49 //如果此結點的權值比 min 結點的權值小,那麼更新 min 結點,否則就是最開始的 min 50 if((*huffmanTree)[i].weight < (*huffmanTree)[min].weight) 51 { 52 min = i; 53 } 54 } 55 } 56 //s2指標指向第二個權值最小的葉子結點 57 *s2 = min; 58 }
構造哈夫曼樹:
1 //建立哈夫曼樹並求哈夫曼編碼的演算法如下,w陣列存放已知的n個權值 2 void createHuffmanTree(HuffmanTree *huffmanTree, float w[], int n) 3 { 4 //m 為哈夫曼樹總共的結點數,n 為葉子結點數 5 int m = 2 * n - 1; 6 //s1 和 s2 為兩個當前結點裡,要選取的最小權值的結點 7 int s1; 8 int s2; 9 //標記 10 int i; 11 // 建立哈夫曼樹的結點所需的空間,m+1,代表其中包含一個頭結點 12 *huffmanTree = (HuffmanTree)malloc((m + 1) * sizeof(Node)); 13 //1--n號存放葉子結點,初始化葉子結點,結構陣列來初始化每個葉子結點,初始的時候看做一個個單個結點的二叉樹 14 for(i = 1; i <= n; i++) 15 { 16 17 //其中葉子結點的權值是 w【n】陣列來儲存 18 (*huffmanTree)[i].weight = w[i]; 19 //初始化葉子結點(單個結點二叉樹)的孩子和雙親,單個結點,也就是沒有孩子和雙親,==0 20 (*huffmanTree)[i].lChild = 0; 21 (*huffmanTree)[i].parent = 0; 22 (*huffmanTree)[i].rChild = 0; 23 }// end of for 24 //非葉子結點的初始化 25 for(i = n + 1; i <= m; i++) 26 { 27 (*huffmanTree)[i].weight = 0; 28 (*huffmanTree)[i].lChild = 0; 29 (*huffmanTree)[i].parent = 0; 30 (*huffmanTree)[i].rChild = 0; 31 } 32 33 printf("\n HuffmanTree: \n"); 34 //建立非葉子結點,建哈夫曼樹 35 for(i = n + 1; i <= m; i++) 36 { 37 38 select(huffmanTree,i-1,&s1,&s2); 39 (*huffmanTree)[s1].parent=i; 40 (*huffmanTree)[s2].parent=i; 41 (*huffmanTree)[i].lChild=s1; 42 (*huffmanTree)[i].rChild=s2; 43 (*huffmanTree)[i].weight=(*huffmanTree)[s1].weight+(*huffmanTree)[s2].weight; 44 printf("%f (%f, %f)\n", (*huffmanTree)[i].weight, (*huffmanTree)[s1].weight, (*huffmanTree)[s2].weight); 45 } 46 printf("\n"); 47 }
求每個節點的哈弗曼編碼:
1 //哈夫曼樹建立完畢,從 n 個葉子結點到根,逆向求每個葉子結點對應的哈夫曼編碼 2 void creatHuffmanCode(HuffmanTree *huffmanTree, HuffmanCode *huffmanCode, int n) 3 { 4 //指示biaoji 5 int i; 6 //編碼的起始指標 7 int start; 8 //指向當前結點的父節點 9 int p; 10 //遍歷 n 個葉子結點的指示標記 c 11 unsigned int c; 12 //分配n個編碼的頭指標 13 huffmanCode = (HuffmanCode *)malloc((n + 1) * sizeof(char *)); 14 //分配求當前編碼的工作空間 15 char *cd = (char *)malloc(n * sizeof(char)); 16 //從右向左逐位存放編碼,首先存放編碼結束符 17 cd[n - 1] = '\0'; 18 //求n個葉子結點對應的哈夫曼編碼 19 for(i = 1; i <= n; i++) 20 { 21 //初始化編碼起始指標 22 start = n - 1; 23 //從葉子到根結點求編碼 24 for(c = i, p = (*huffmanTree)[i].parent; p != 0; c = p, p = (*huffmanTree)[p].parent) 25 { 26 if( (*huffmanTree)[p].lChild == c) 27 { 28 //從右到左的順序編碼入陣列內 29 cd[--start] = '0'; //左分支標0 30 } 31 else 32 { 33 cd[--start] = '1'; //右分支標1 34 } 35 }// end of for 36 //為第i個編碼分配空間 37 huffmanCode[i] = (char *)malloc((n - start) * sizeof(char)); 38 strcpy(huffmanCode[i], &cd[start]); 39 } 40 41 free(cd); 42 //列印編碼序列 43 for(i = 1; i <= n; i++) 44 { 45 printf("HuffmanCode of %d is %s\n", i, huffmanCode[i]); 46 } 47 48 printf("\n"); 49 }
輸入一串資料(資料為1—9之間從小到大連續的數),編譯成哈夫曼編碼:
1 HuffmanCode *creatHuffmanCode1(HuffmanTree *huffmanTree,char *Huffman, int n,int data[],float num){ 2 //該函式通過改寫creatHuffmanCode函式,來實現譯碼(即是通過匹配即新增字串的方式來實現) 3 HuffmanCode *huffmanCode; 4 huffmanCode = (HuffmanCode *)malloc((n + 1) * sizeof(char *)); 5 int i; 6 int start; 7 int p; 8 unsigned int c; 9 char *cd = (char *)malloc(n * sizeof(char)); 10 cd[n - 1] = '\0'; 11 for(i = 1; i <= n; i++) 12 { 13 start = n - 1; 14 for(c = i, p = (*huffmanTree)[i].parent; p != 0; c = p, p = (*huffmanTree)[p].parent) 15 { 16 if( (*huffmanTree)[p].lChild == c) 17 { 18 cd[--start] = '0'; 19 } 20 else 21 { 22 cd[--start] = '1'; 23 } 24 } 25 huffmanCode[i] = (char *)malloc((n - start) * sizeof(char)); 26 strcpy(huffmanCode[i], &cd[start]); 27 } 28 free(cd); 29 //列印編碼序列 30 int num1=int(num); 31 int l=0; 32 char s[10]=""; 33 itoa(n,s,10); 34 huffmanCode[0] = (char *)malloc((10) * sizeof(char)); 35 strcpy(huffmanCode[0],s); 36 for(int i=1;i<=num1;i++){ 37 int z=data[i]; 38 strcat(Huffman,huffmanCode[z]); 39 } 40 printf("HuffmanCode of this String is %s\n",Huffman); 41 printf("\n"); 42 return huffmanCode; 43 } 44 45 46 HuffmanCode *dataToHuffmanCode(char *Huffman, int data[]){ 47 //data陣列是原資料(0—9),Huffman存放編譯後的 Huffman編碼 48 HuffmanTree huffmanTree; 49 int k=1; 50 int q=0; 51 while(data[k]!=-99){ 52 k++; 53 } 54 float num=float(k-1); 55 float val[10]; 56 for(int j=1;j<10;j++){ 57 int i=1; 58 int n=0; 59 while(data[i]!=-99){ 60 if(data[i]==j){ 61 n++; 62 } 63 i++; 64 } 65 val[j]=n/num; 66 } 67 for(int i=1;i<10;i++){ 68 if(val[i]!=0){ 69 q++; //q為葉子結點數 70 } 71 } 72 float *val2; 73 val2 = (float *)malloc((q+1) * sizeof(float)); 74 int m=1; 75 for(int i=1;i<10;i++){ 76 if(val[i]!=0){ 77 val2[m]=val[i]; 78 } 79 m++; //val2[]儲存不同元素的權值 80 } 81 createHuffmanTree(&huffmanTree,val2,q); 82 HuffmanCode *huffmanCode=creatHuffmanCode1(&huffmanTree,Huffman,q,data,num);//呼叫上面的 creatHuffmanCode1函式 83 return huffmanCode; 84 }
將一串哈夫曼編碼,還原成原來的資料(此時哈弗曼樹已經構造完成):
1 void HuffmanCodeTodata(char *Huffman, int data[],HuffmanCode *huffmanCode){ 2 //huffmanCode為已經構造好的一顆Huffman樹,Huffman存放已經編譯好的 Huffman編碼 ,data陣列存放解碼後的原資料(0—9) 3 4 printf("Huffman:%s\n",Huffman); 5 printf("1:%s\n",huffmanCode[1]); 6 printf("2:%s\n",huffmanCode[2]); 7 printf("3:%s\n",huffmanCode[3]); 8 printf("4:%s\n",huffmanCode[4]); 9 //這幾行是顯示功能,和實現無關 10 11 int num=atoi(huffmanCode[0]); 12 char Huffman1[100]=""; 13 int k1=0; 14 int k2=0; 15 int x=1; 16 int y=1; 17 while(Huffman[k1]=='1'||Huffman[k1]=='0'){ 18 Huffman1[k2]=Huffman[k1]; 19 for(int i=1;i<=num;i++){ 20 if(strcmp(huffmanCode[i],Huffman1)==0){ 21 k2=-1; 22 data[x]=i; 23 x++; 24 //strcpy(Huffman1,""); 25 memset(Huffman1,0,sizeof(Huffman1)); 26 break; 27 } 28 } 29 k1++; 30 k2++; 31 } 32 data[x]=-99; 33 for(int i=1;i<100;i++){ 34 if(data[i]!=-99){ 35 printf("%d",data[i]); 36 } 37 else{ 38 break; 39 } 40 } 41 }
輸入一個文字,統計文字中各字串的數目,計算相應的權重,使用該權重構建哈夫曼編碼,使用該編碼編譯原檔案
(即是在編碼的基礎上加上檔案操作):
1 void fileHuff(){ 2 FILE *in; 3 char Huffman2[1000]=""; 4 int data[100]; 5 char file[10]; 6 int k = 1; 7 printf("輸入讀取的檔名:(輸入aaa.txt)"); 8 scanf("%s",file); 9 if((in=fopen(file,"r+"))==NULL){ 10 printf("無法開啟此檔案!\n"); 11 exit(0); 12 } 13 while(!feof(in)){ 14 fscanf(in,"%d",&data[k++]); 15 } 16 for(int i=1;i<k;i++){ 17 printf("\n%d\n",data[i]); 18 } 19 data[k]=-99; 20 dataToHuffmanCode(Huffman2,data); 21 fprintf(in,"\n"); 22 fprintf(in,"解碼後的碼為:\n"); 23 fprintf(in,"%s",Huffman2); 24 fflush(in); 25 fclose(in); 26 }
主函式:
1 int main() 2 { 3 HuffmanTree HT; 4 HuffmanCode HC; 5 char Huffman[1000]=""; 6 char Huffman1[1000]=""; 7 HuffmanCode *huffmanCode; 8 int i, n; 9 float *w, wei; 10 int choice; 11 for (;;) 12 { 13 printf("\n 哈夫曼編碼 \n"); 14 printf(" 1.哈夫曼編碼實現\n"); 15 printf(" 2.輸入一串資料(資料為1—9之間從小到大連續的數),編譯成哈夫曼編碼\n"); 16 printf(" 3.將一串哈夫曼編碼,還原成原來的資料\n"); 17 printf(" 4.輸入一個文字,統計文字中各字串的數目,計算相應的權重,使用該權重構建哈夫曼編碼,使用該編碼編譯原檔案\n"); 18 printf(" 5.退出系統\n"); 19 printf("請選擇:"); 20 scanf("%d", &choice); 21 switch (choice) 22 { 23 case 1: 24 printf("\n 需要建立的檔案有多少個碼元 = " ); 25 scanf("%d", &n); 26 w = (float *)malloc((n + 1) * sizeof(float)); 27 printf("\ninput the %d element's weight:\n", n); 28 for(i = 1; i <= n; i++) 29 { 30 printf("%d: ", i); 31 fflush(stdin); 32 scanf("%f", &wei); 33 w[i] = wei; 34 } 35 createHuffmanTree(&HT, w, n); 36 creatHuffmanCode(&HT, &HC, n); 37 break; 38 case 2: 39 printf("\n輸入資料為:(以-99結束)"); 40 int data[100]; 41 for(int i=1;i<100;i++){ 42 scanf("%d",&data[i]); 43 if(data[i]==-99){ 44 break; 45 } 46 } 47 huffmanCode=dataToHuffmanCode(Huffman,data); 48 break; 49 case 3: 50 int data1[100]; 51 printf("\n哈夫曼編碼解碼後為:\n"); 52 HuffmanCodeTodata(Huffman,data1,huffmanCode); 53 break; 54 case 4: 55 printf("\n需要實現將aaa.txt檔案放在桌面上!\n"); 56 fileHuff(); 57 break; 58 case 5: 59 printf("請退出系統!\n"); 60 exit(0); 61 break; 62 } 63 } 64 return 1; 65 }