姓名:周小蓬 16019110037
轉載自:http://blog.csdn.net/F__shigang/article/details/65442550
[嵌牛導讀]
Huffman樹是一類帶權路徑長度WPL最短的二叉樹,中文名叫哈夫曼樹或最優二叉樹。
相關概念:
結點的路徑長度:從根結點到該結點的路徑上分支的數目。
樹的路徑長度:樹中每個結點的路徑長度之和。
樹的帶權路徑長度:樹中所有葉子結點的帶權路徑長度之和。
[嵌牛鼻子]
c語言
[嵌牛提問]
如何學習哈夫曼樹以及步驟
[嵌牛正文]
構造Huffman樹的步驟:
1)? 根據給定的n個權值,構造n棵只有一個根結點的二叉樹,n個權值分別是這些二叉樹根結點的權;
2)? 設F是由這n棵二叉樹構成的集合,在F中選取兩棵根結點權值最小的樹作為左、右子樹,構造成一顆新的二叉樹,置新二叉樹根結點的權值等于左、右子樹根結點的權值之和。為了使得到的哈夫曼樹的結構唯一,規定根結點權值最小的作為新二叉樹的左子樹。
3)? 從F中刪除這兩棵樹,并將新樹加入F;
4)? 重復2)、3)步,直到F中只含一棵樹為止,這棵樹便是Huffman樹。
說明:n個結點需要進行n-1次合并,每次合并都產生一個新的結點,最終的Huffman樹共有2n-1個結點。
2、Huffman編碼
Huffman樹在通訊編碼中的一個應用:
利用哈夫曼樹構造一組最優前綴編碼。主要用途是實現數據壓縮。在某些通訊場合,需將傳送的文字轉換成由二進制字符組成的字符串進行傳輸。
方法:
利用哈夫曼樹構造一種不等長的二進制編碼,并且構造所得的哈夫曼編碼是一種最優前綴編碼,使所傳電文的總長度最短。
不等長編碼:即各個字符的編碼長度不等(如:0,10,110,011),可以使傳送電文的字符串的總長度盡可能地短。對出現頻率高的字符采用盡可能短的編碼,則傳送電文的總長度便盡可能短。
前綴編碼:任何一個字符的編碼都不是同一字符集中另一個字符的編碼的前綴。
二、代碼實現
使用鏈表結構構建哈夫曼樹并進行編碼、解碼,代碼如下:
[cpp]view plaincopy
#include?
#include?
#include?
typedefintELEMTYPE;
//?哈夫曼樹結點結構體
typedefstructHuffmanTree
{
ELEMTYPE?weight;
ELEMTYPE?id;//?id用來主要用以區分權值相同的結點,這里代表了下標
structHuffmanTree*?lchild;
structHuffmanTree*?rchild;
}HuffmanNode;
//?構建哈夫曼樹
HuffmanNode*?createHuffmanTree(int*?a,intn)
{
inti,?j;
HuffmanNode?**temp,?*hufmTree;
temp?=?malloc(n*sizeof(HuffmanNode));
for(i=0;?i
{
temp[i]?=?(HuffmanNode*)malloc(sizeof(HuffmanNode));
temp[i]->weight?=?a[i];
temp[i]->id?=?i;
temp[i]->lchild?=?temp[i]->rchild?=?NULL;
}
for(i=0;?i
{
intsmall1=-1,?small2;//?small1、small2分別作為最小和次小權值的下標
for(j=0;?j
{
if(temp[j]?!=?NULL?&&?small1==-1)
{
small1?=?j;
continue;
}elseif(temp[j]?!=?NULL)
{
small2?=?j;
break;
}
}
for(j=small2;?j
{
if(temp[j]?!=?NULL)
{
if(temp[j]->weight?<?temp[small1]->weight)
{
small2?=?small1;
small1?=?j;
}elseif(temp[j]->weight?<?temp[small2]->weight)
{
small2?=?j;
}
}
}
hufmTree?=?(HuffmanNode*)malloc(sizeof(HuffmanNode));
hufmTree->weight?=?temp[small1]->weight?+?temp[small2]->weight;
hufmTree->lchild?=?temp[small1];
hufmTree->rchild?=?temp[small2];
temp[small1]?=?hufmTree;
temp[small2]?=?NULL;
}
free(temp);
returnhufmTree;
}
//?以廣義表的形式打印哈夫曼樹
voidPrintHuffmanTree(HuffmanNode*?hufmTree)
{
if(hufmTree)
{
printf("%d",?hufmTree->weight);
if(hufmTree->lchild?!=?NULL?||?hufmTree->rchild?!=?NULL)
{
printf("(");
PrintHuffmanTree(hufmTree->lchild);
printf(",");
PrintHuffmanTree(hufmTree->rchild);
printf(")");
}
}
}
//?遞歸進行哈夫曼編碼
voidHuffmanCode(HuffmanNode*?hufmTree,intdepth)//?depth是哈夫曼樹的深度
{
staticintcode[10];
if(hufmTree)
{
if(hufmTree->lchild==NULL?&&?hufmTree->rchild==NULL)
{
printf("id為%d權值為%d的葉子結點的哈夫曼編碼為?",?hufmTree->id,?hufmTree->weight);
inti;
for(i=0;?i
{
printf("%d",?code[i]);
}
printf("\n");
}else
{
code[depth]?=?0;
HuffmanCode(hufmTree->lchild,?depth+1);
code[depth]?=?1;
HuffmanCode(hufmTree->rchild,?depth+1);
}
}
}
//?哈夫曼解碼
voidHuffmanDecode(charch[],?HuffmanNode*?hufmTree,charstring[])//?ch是要解碼的01串,string是結點對應的字符
{
inti;
intnum[100];
HuffmanNode*?tempTree?=?NULL;
for(i=0;?i
{
if(ch[i]?=='0')
num[i]?=?0;
else
num[i]?=?1;
}
if(hufmTree)
{
i?=?0;//?計數已解碼01串的長度
while(i
{
tempTree?=?hufmTree;
while(tempTree->lchild!=NULL?&&?tempTree->rchild!=NULL)
{
if(num[i]?==?0)
{
tempTree?=?tempTree->lchild;
}else
{
tempTree?=?tempTree->rchild;
}
++i;
}
printf("%c",?string[tempTree->id]);//?輸出解碼后對應結點的字符
}
}
}
intmain()
{
inti,?n;
printf("請輸入葉子結點的個數:\n");
while(1)
{
scanf("%d",?&n);
if(n>1)
break;
else
printf("輸入錯誤,請重新輸入n值!");
}
int*?arr;
arr=(int*)malloc(n*sizeof(ELEMTYPE));
printf("請輸入%d個葉子結點的權值:\n",?n);
for(i=0;?i
{
scanf("%d",?&arr[i]);
}
charch[100],?string[100];
printf("請連續輸入這%d個葉子結點各自所代表的字符:\n",?n);
fflush(stdin);//?強行清除緩存中的數據,也就是上面輸入權值結束時的回車符
gets(string);
HuffmanNode*?hufmTree?=?NULL;
hufmTree?=?createHuffmanTree(arr,?n);
printf("此哈夫曼樹的廣義表形式為:\n");
PrintHuffmanTree(hufmTree);
printf("\n各葉子結點的哈夫曼編碼為:\n");
HuffmanCode(hufmTree,?0);
printf("要解碼嗎?請輸入編碼:\n");
gets(ch);
printf("解碼結果為:\n");
HuffmanDecode(ch,?hufmTree,?string);
printf("\n");
free(arr);
free(hufmTree);
return0;
}
運行結果如圖:
三、程序實現過程當中遇到的問題總結
1)關于哈夫曼樹,知道了葉子結點,如何不用靜態數組存儲整個哈夫曼樹及構建過程中的生成樹?
答:使用malloc函數開辟一段內存空間存結構體類型的樹,若往樹中添加新的結點掛在結構體指針上即可,這就要求定義的結構體里面包含結構體指針,這也是結構體指針的作用。也就是使用鏈表動態存儲,每個結點都是一個包含結構體指針的結構體,生成過程中動態開辟,不管這棵樹有多少個結點都可以存下。
2)
[cpp]view plaincopy
typedefstructstHuNode
{
intdata;//?權值
structstHuNode*?lchild;//這兩句等價于?struct?stHuNode*?lchild,?*rchild;
structstHuNode*?rchild;
}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信息,這樣即使權值相同的結點也可以區分開來,這里的id可以是下標,因為用戶輸入權值的順序一定則下標唯一。如果解碼解出來的是權值標號的話就沒有異議了,可是下標又不是很直觀清晰,不如直接輸出相應的字符好,又想到兩個解決辦法:a)將結點id信息直接定義成字符,只不過在建樹的過程中要將字符和權值都加入結點中;b)id仍然是下標,在用戶輸入權值對應的字符時,用字符數組存儲并和id對應起來,這樣解碼得到id之后,可以輸出對應的字符,實現起來相對比較簡單。
5)疑問:如果根據用戶輸入的字符串進行編碼解碼得到了新的字符串,舊字符串和新字符串有沒有直接看出來的規律,就是說人眼觀察和推導能否得到相應的規律,或者說有沒有可能直接能得出規律不用經過編碼解碼。留為疑問!
答:后經測試發現不行。
6)gets()函數的用法,如何獲取一個字符串,賦值時跳過gets()函數的執行,貌似gets()沒起作用的問題。
答:當使用gets()函數之前有過數據輸入,并且,操作者輸入了回車確認,這個回車符沒有被清理,被保存在輸入緩存中時,gets()會讀到這個字符,結束讀字符操作。因此,從用戶表面上看,gets()沒有起作用,跳過了。
解決辦法:
方法一、在gets()前加fflush(stdin); //強行清除緩存中的數據(windows下可行)
方法二、根據程序代碼,確定前面是否有輸入語句,如果有,則增加一個getchar()命令,然后再調用 gets()命令。
方法三、檢查輸入結果,如果得到的字符串是空串,則繼續讀入,如:
[cpp]view plaincopy
charstr[100]={0};
do{
gets(str);
}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()是函數。