h5房卡源碼C語言實現哈夫曼樹、編碼、解碼及問題總結

1、準備知識windows

一、Huffman樹數組

Huffman樹是一類帶權路徑長度WPL最短的二叉樹,中文名叫哈夫曼樹或最優二叉樹。緩存

相關概念:函數

結點的路徑長度:從根結點到該結點的路徑上分支的數目。測試

樹的路徑長度:樹中每一個結點的路徑長度之和。編碼

樹的帶權路徑長度:樹中全部葉子結點的帶權路徑長度之和。spa

 

構造Huffman樹的步驟:.net

1)  根據給定的n個權值,構造n棵只有一個根結點的二叉樹,n個權值分別是這些二叉樹根結點的權;指針

2)  設F是由這n棵二叉樹構成的集合,在F中選取兩棵根結點權值最小的樹做爲左、右子樹,構形成一顆新的二叉樹,置新二叉樹根結點的權值等於左、右子樹根結點的權值之和。爲了使獲得的哈夫曼樹的結構惟一,規定根結點權值最小的做爲新二叉樹的左子樹。code

3)  從F中刪除這兩棵樹,並將新樹加入F;

4)  重複2)、3)步,直到F中只含一棵樹爲止,這棵樹即是Huffman樹。

說明:n個結點須要進行n-1次合併,每次合併都產生一個新的結點,最終的Huffman樹共有2n-1個結點。

二、Huffman編碼

Huffman樹在通信編碼中的一個應用:

利用哈夫曼樹構造一組最優前綴編碼。主要用途是實現數據壓縮。在某些通信場合,需將傳送的文字轉換成由二進制字符組成的字符串進行傳輸。

方法:

利用哈夫曼樹構造一種不等長的二進制編碼,而且構造所得的哈夫曼編碼是一種最優前綴編碼,使所傳電文的總長度最短。

不等長編碼:即各個字符的編碼長度不等(如:0,10,110,011),可使傳送電文的字符串的總長度儘量地短。對出現頻率高的字符采用盡量短的編碼,則傳送電文的總長度便儘量短。

前綴編碼:任何一個字符的編碼都不是同一字符集中另外一個字符的編碼的前綴。

 

2、代碼實現

使用鏈表結構構建哈夫曼樹並進行編碼、解碼,代碼以下:

[cpp]  view plain  copy
 
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4.   
  5. typedef int ELEMTYPE;  
  6.   
  7. // 哈夫曼樹結點結構體  
  8. typedef struct HuffmanTree  
  9. {  
  10.     ELEMTYPE weight;  
  11.     ELEMTYPE id;        // id用來主要用以區分權值相同的結點,這裏表明了下標  
  12.     struct HuffmanTree* lchild;  
  13.     struct HuffmanTree* rchild;  
  14. }HuffmanNode;  
  15.   
  16. // 構建哈夫曼樹  
  17. HuffmanNode* createHuffmanTree(int* a, int n)  
  18. {  
  19.     int i, j;  
  20.     HuffmanNode **temp, *hufmTree;  
  21.     temp = malloc(n*sizeof(HuffmanNode));  
  22.     for (i=0; i<n; ++i)     // 將數組a中的權值賦給結點中的weight  
  23.     {  
  24.         temp[i] = (HuffmanNode*)malloc(sizeof(HuffmanNode));  
  25.         temp[i]->weight = a[i];  
  26.         temp[i]->id = i;  
  27.         temp[i]->lchild = temp[i]->rchild = NULL;  
  28.     }  
  29.   
  30.     for (i=0; i<n-1; ++i)       // 構建哈夫曼樹須要n-1合併  
  31.     {  
  32.         int small1=-1, small2;      // small一、small2分別做爲最小和次小權值的下標  
  33.         for (j=0; j<n; ++j)         // 先將最小的兩個下標賦給small一、small2(注意:對應權值未必最小)  
  34.         {  
  35.             if (temp[j] != NULL && small1==-1)  
  36.             {  
  37.                 small1 = j;  
  38.                 continue;  
  39.             } else if(temp[j] != NULL)  
  40.             {  
  41.                 small2 = j;  
  42.                 break;  
  43.             }  
  44.         }  
  45.   
  46.         for (j=small2; j<n; ++j)    // 比較權值,挪動small1和small2使之分別成爲最小和次小權值的下標  
  47.         {  
  48.             if (temp[j] != NULL)  
  49.             {  
  50.                 if (temp[j]->weight < temp[small1]->weight)  
  51.                 {  
  52.                     small2 = small1;  
  53.                     small1 = j;  
  54.                 } else if (temp[j]->weight < temp[small2]->weight)  
  55.                 {  
  56.                     small2 = j;  
  57.                 }  
  58.             }  
  59.         }  
  60.         hufmTree = (HuffmanNode*)malloc(sizeof(HuffmanNode));  
  61.         hufmTree->weight = temp[small1]->weight + temp[small2]->weight;  
  62.         hufmTree->lchild = temp[small1];  
  63.         hufmTree->rchild = temp[small2];  
  64.   
  65.         temp[small1] = hufmTree;  
  66.         temp[small2] = NULL;  
  67.     }  
  68.     free(temp);  
  69.     return hufmTree;  
  70. }  
  71.   
  72. // 以廣義表的形式打印哈夫曼樹  
  73. void PrintHuffmanTree(HuffmanNode* hufmTree)  
  74. {  
  75.     if (hufmTree)  
  76.     {  
  77.         printf("%d", hufmTree->weight);  
  78.         if (hufmTree->lchild != NULL || hufmTree->rchild != NULL)  
  79.         {  
  80.             printf("(");  
  81.             PrintHuffmanTree(hufmTree->lchild);  
  82.             printf(",");  
  83.             PrintHuffmanTree(hufmTree->rchild);  
  84.             printf(")");  
  85.         }  
  86.     }  
  87. }  
  88.   
  89. // 遞歸進行哈夫曼編碼  
  90. void HuffmanCode(HuffmanNode* hufmTree, int depth)      // depth是哈夫曼樹的深度  
  91. {  
  92.     static int code[10];  
  93.     if (hufmTree)  
  94.     {  
  95.         if (hufmTree->lchild==NULL && hufmTree->rchild==NULL)  
  96.         {  
  97.             printf("id爲%d權值爲%d的葉子結點的哈夫曼編碼爲 ", hufmTree->id, hufmTree->weight);  
  98.             int i;  
  99.             for (i=0; i<depth; ++i)  
  100.             {  
  101.                 printf("%d", code[i]);  
  102.             }  
  103.             printf("\n");  
  104.         } else  
  105.         {  
  106.             code[depth] = 0;  
  107.             HuffmanCode(hufmTree->lchild, depth+1);  
  108.             code[depth] = 1;  
  109.             HuffmanCode(hufmTree->rchild, depth+1);  
  110.         }  
  111.     }  
  112. }  
  113.   
  114. // 哈夫曼解碼  
  115. void HuffmanDecode(char ch[], HuffmanNode* hufmTree, char string[])     // ch是要解碼的01串,string是結點對應的字符  
  116. {  
  117.     int i;  
  118.     int num[100];  
  119.     HuffmanNode* tempTree = NULL;  
  120.     for (i=0; i<strlen(ch); ++i)  
  121.     {  
  122.         if (ch[i] == '0')  
  123.             num[i] = 0;  
  124.         else  
  125.             num[i] = 1;  
  126.     }  
  127.     if(hufmTree)  
  128.     {  
  129.         i = 0;      // 計數已解碼01串的長度  
  130.         while(i<strlen(ch))  
  131.         {  
  132.             tempTree = hufmTree;  
  133.             while(tempTree->lchild!=NULL && tempTree->rchild!=NULL)  
  134.             {  
  135.                 if (num[i] == 0)  
  136.                 {  
  137.                     tempTree = tempTree->lchild;  
  138.                 } else  
  139.                 {  
  140.                     tempTree = tempTree->rchild;  
  141.                 }  
  142.                 ++i;  
  143.             }  
  144.             printf("%c", string[tempTree->id]);     // 輸出解碼後對應結點的字符  
  145.         }  
  146.     }  
  147. }  
  148.   
  149. int main()  
  150. {  
  151.     int i, n;  
  152.     printf("請輸入葉子結點的個數:\n");  
  153.     while(1)  
  154.     {  
  155.         scanf("%d", &n);  
  156.         if (n>1)  
  157.             break;  
  158.         else  
  159.             printf("輸入錯誤,請從新輸入n值!");  
  160.     }  
  161.   
  162.     int* arr;  
  163.     arr=(int*)malloc(n*sizeof(ELEMTYPE));  
  164.     printf("請輸入%d個葉子結點的權值:\n", n);  
  165.     for (i=0; i<n; ++i)  
  166.     {  
  167.         scanf("%d", &arr[i]);  
  168.     }  
  169.   
  170.     char ch[100], string[100];  
  171.     printf("請連續輸入這%d個葉子結點各自所表明的字符:\n", n);  
  172.     fflush(stdin);      // 強行清除緩存中的數據,也就是上面輸入權值結束時的回車符  
  173.     gets(string);  
  174.   
  175.     HuffmanNode* hufmTree = NULL;  
  176.     hufmTree = createHuffmanTree(arr, n);  
  177.   
  178.     printf("此哈夫曼樹的廣義表形式爲:\n");  
  179.     PrintHuffmanTree(hufmTree);  
  180.     printf("\n各葉子結點的哈夫曼編碼爲:\n");  
  181.     HuffmanCode(hufmTree, 0);  
  182.   
  183.     printf("要解碼嗎?請輸入編碼:\n");  
  184.     gets(ch);  
  185.     printf("解碼結果爲:\n");  
  186.     HuffmanDecode(ch, hufmTree, string);  
  187.     printf("\n");  
  188.   
  189.     free(arr);  
  190.     free(hufmTree);  
  191.   
  192.     return 0;  
  193. }  

運行結果如圖:

3、程序實現過程中遇到的問題總結

1)關於哈夫曼樹,知道了葉子結點,如何不用靜態數組存儲整個哈夫曼樹及構建過程當中的生成樹?

答:使用malloc函數開闢一段內存空間存結構體類型的樹,若往樹中添加新的結點掛在結構體指針上便可,這就要求定義的結構體裏面包含結構體指針,這也是結構體指針的做用。也就是使用鏈表動態存儲,每一個結點都是一個包含結構體指針的結構體,生成過程當中動態開闢,無論這棵樹有多少個結點均可以存下。

2)

[cpp]  view plain  copy
 
  1. typedef struct stHuNode  
  2. {  
  3.     int data;       // 權值  
  4.     struct stHuNode* lchild;        //這兩句等價於 struct stHuNode* lchild, *rchild;  
  5.     struct stHuNode* rchild;      
  6. }HUNODE;  

3)scanf語句裏不要有換行符?scanf函數的用法,scanf(" %d", &i);和scanf("%d ",&i);效果不一樣,差異在哪?

答:scanf函數的通常形式爲:scanf(「格式控制字符串」, 地址表列);格式控制字符串中不能顯示非格式字符串,也就是不能顯示提示字符串和換行符。」 %d」 和「%d」做用同樣,%d前面的空格不起做用,」%d 「空格加在%d後面賦值時要多輸入一個值,實際賦值操做時多輸入的數並無被賦值只是緩存到了輸入流stdin中,下次若是再有scanf和gets語句要求賦值時,要先用fflush(stdin);語句強制清除緩存再賦值,不然原先在stdin中的值就會被賦過去致使錯誤。

4)靈感:怎樣解碼?根據輸入的01串解出相應的權值?

答:No!若是兩個不一樣的字符對應的權值相同呢?如何區分?起初想到若是在創建哈夫曼樹的過程當中能夠記錄下相應的下標就不會致使相同權值沒法區別的問題,但在具體如何實現上,剛開始想輸出每一次建樹的small1和small2,但發現這樣很不清晰,用戶要想肯定每一個字符的下標得按照建樹過程走一遍才行,那要程序何用,並且給用戶形成了很大的麻煩,不可取。後來想到,能夠在結點的結構體中添加id信息,這樣即便權值相同的結點也能夠區分開來,h5房卡源碼h5.mostsheng.com這裏的id能夠是下標,由於用戶輸入權值的順序必定則下標惟一。若是解碼解出來的是權值標號的話就沒有異議了,但是下標又不是很直觀清晰,不如直接輸出相應的字符好,又想到兩個解決辦法:a)將結點id信息直接定義成字符,只不過在建樹的過程當中要將字符和權值都加入結點中;b)id仍然是下標,在用戶輸入權值對應的字符時,用字符數組存儲並和id對應起來,這樣解碼獲得id以後,能夠輸出對應的字符,實現起來相對比較簡單。

5)疑問:若是根據用戶輸入的字符串進行編碼解碼獲得了新的字符串,舊字符串和新字符串有沒有直接看出來的規律,就是說人眼觀察和推導可否獲得相應的規律,或者說有沒有可能直接能得出規律不用通過編碼解碼。留爲疑問!

答:後經測試發現不行。

6)gets()函數的用法,如何獲取一個字符串,賦值時跳過gets()函數的執行,貌似gets()沒起做用的問題。

答:當使用gets()函數以前有過數據輸入,而且,操做者輸入了回車確認,這個回車符沒有被清理,被保存在輸入緩存中時,gets()會讀到這個字符,結束讀字符操做。所以,從用戶表面上看,gets()沒有起做用,跳過了。

解決辦法:

方法1、在gets()前加fflush(stdin); //強行清除緩存中的數據(windows下可行)

方法2、根據程序代碼,肯定前面是否有輸入語句,若是有,則增長一個getchar()命令,而後再調用 gets()命令。

方法3、檢查輸入結果,若是獲得的字符串是空串,則繼續讀入,如:

[cpp]  view plain  copy
 
  1. char str[100]={0};  
  2. do {  
  3.     gets(str);  
  4. while( !str[0] );  

7)初始化數組語句:memset(str,0, sizeof(str)); 理解一下!

答:函數解釋:將 str 中前 n 個字節用 ch 替換並返回 str。memset(str, 0, sizeof(str));意思是將數組str的長度(字節數,不是元素個數)置零。

memset:做用是在一段內存塊中填充某個給定的值,它是對較大的結構體或數組進行清零操做的一種最快方法。

8)關於strlen()函數

答:strlen函數的用法,包含頭文件string.h。且是對字符數組中的字符串求長度!

其原型爲:  unsigned int strlen (char *s);

【參數說明】s爲指定的字符串。

strlen()用來計算指定的字符串s 的長度,不包括結束字符"\0"。

【返回值】返回字符串s 的字符數。

注意:strlen() 函數計算的是字符串的實際長度,遇到第一個'\0'結束。若是你只定義沒有給它賦初值,這個結果是不定的,它會從首地址一直找下去,直到遇到'\0'中止。而sizeof返回的是變量聲明後所佔的內存數,不是實際長度,此外sizeof不是函數,僅僅是一個操做符,strlen()是函數。

相關文章
相關標籤/搜索