1. 程式人生 > >大學實驗1 哈夫曼編碼

大學實驗1 哈夫曼編碼

一、實驗目的 理解貪心法思想,掌握構造哈夫曼樹的方法及哈夫曼編碼的生成。

二、實驗內容

按要求編寫程式,次都選取未構造過的權值最小的葉子結點來構造哈夫曼樹,最後根

據哈夫曼編碼規則求出哈夫曼編碼。 三、實驗步驟 步驟1:引入必要的函式庫。

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

步驟2:定義結構體與別名。

typedef struct

{

int weight; //權值 int parent; //父結點序號 int left; //左子樹序號 int right; //右子樹序號

}HuffmanTree;

typedef char *HuffmanCode; //Huffman編碼

步驟3:實現函式 SelectNode()

函式作用:從 n 個結點中選擇"parent 域取值為 0 weight 域取值小"的兩個結點,並利用 引數 bt1,bt2 返回這兩個結點在陣列 ht[]中的下標。 引數含義 ht:指向 HuffmanTree 型別陣列的首地址,該陣列存放 n HuffmanTree 型別結點,這

些結點對應於 Huffman 樹中的葉結點或中間結點。 bt1,bt2:指標型別的引數,用於返回 HuffmanTree 型別陣列 ht[]中"parent 域取值為零

weight 域取值最小"的兩個結點的下標。 函式內部變含義

ht1:儲存陣列 ht[]中"weight 域取值最小"的結點的記憶體地址。 ht1-ht:陣列 ht[]中"weight 域取值最小"的結點在陣列 ht 中的下標。 ht2:儲存陣列 ht[]中"weight 域取值次小"的結點的記憶體地址。

ht2-ht:陣列 ht[]中"weight 域取值次小"的結點在陣列 ht 中的下標。

void SelectNode(HuffmanTree *ht,int n,int *bt1,int *bt2)

{ int i;

HuffmanTree *ht1,*ht2,*t;

ht1=ht2=NULL; //初始化兩個結點為空

/*依次檢查ht[]中的每個結點(包括葉結點和中間結點)。若當前結點ht[i]沒有雙親, 則分以下情況進行處理:1)ht1取值為空。2)ht2取值為空。3)ht1與ht2取值為非空。*/

for(i=1;i<=n;++i)

{ if(!ht[i].parent) //父結點為空(結點的parent=0)

{ if(ht1==NULL) //情況1:指標ht1為空

{

ht1=ht+i; //指向第i個結點,將其作為"當前weight域最小的"結點

continue; //退出當前輪次的for迴圈

} if(ht2==NULL) //情況2:指標ht2為空

{ ht2=ht+i; //指向第i個結點

/*若ht2所指結點的weight域取值更小,則對換ht1與ht2,使得ht1

始終指向weight域取值更小的那個結點*/

if(ht1->weight>ht2->weight)

{ t=ht2; ht2=ht1; ht1=t; } continue; //退出當前輪次的for迴圈

} if(ht1 && ht2) //情況3:若ht1、ht2兩個指標都有效

{

//第i個結點權重小於ht1指向的結點 if(ht[i].weight<=ht1->weight)

{

ht2=ht1; //ht2儲存ht1,因為這時ht1指向的結點成為第2小的

ht1=ht+i; //ht1指向第i個結點

}

//若第i個結點權重小於ht2指向的結點 else if(ht[i].weight<ht2->weight){

ht2=ht+i; //ht2指向第i個結點

}

}

}

}

/*對ht1與ht2進行比較,使得權值最小的兩個結點按照它們在陣列ht[]中最初的排列順序出現。換言之,當兩個權值相等時,使得Huffman樹的左側為葉結點,而不是中間結點(因為,葉結點在陣列ht[]中的下標更小)*/

if(ht1>ht2){ *bt2=ht1-ht;

*bt1=ht2-ht;

}else{

*bt1=ht1-ht;

*bt2=ht2-ht;

}

}

步驟4:實現函式 CreateTree() 函式作用:根據結點陣列 ht[]和權重陣列 w[],建立 Huffman 樹。 引數含義 htHuffmanTree 型別陣列的首地址。注意,在陣列 ht[]中,單元 ht[0]並不使用,單元

ht[1..n]中存放的是葉結點,單元 ht[n+1..m]中存放的是中間結點。

n:陣列的長度。

w:權重陣列的首地址。

void CreateTree(HuffmanTree *ht,int n,int *w)

{

int i,m=2*n-1;//總的結點數量

int bt1,bt2;

if(n<=1) return ; //只有一個結點,無法建立 for(i=1;i<=n;++i) //初始化葉結點

{

ht[i].weight=w[i-1]; ht[i].parent=0; ht[i].left=0; ht[i].right=0;

}

for(;i<=m;++i)//初始化中間結點(陣列ht[n+1..m]中儲存的是中間結點)

{

ht[i].weight=0; ht[i].parent=0; ht[i].left=0; ht[i].right=0;

}

//逐個計算非葉結點的資訊,建立Huffman樹 for(i=n+1;i<=m;++i)

{

/*從陣列ht[]的第1~i-1個單元中選擇"parent域取值0且weight域取值最小"的兩個結點,即ht[bt1]和ht[bt2]*/ SelectNode(ht,i-1,&bt1,&bt2);

//將當前結點ht[i]作為結點ht[bt1]的雙親

ht[bt1].parent=i;

//將當前結點ht[i]作為結點ht[bt2]的雙親 ht[bt2].parent=i;

//將結點ht[bt1]作為當前結點ht[i]的左孩子 ht[i].left=bt1;

//將結點ht[bt2]作為當前結點ht[i]的右孩子 ht[i].right=bt2;

//根據左孩子ht[bt1]和右孩子ht[bt2]的weight域計算產生當前結點ht[i]的weight域 ht[i].weight=ht[bt1].weight+ht[bt2].weight;

}

}

步驟5:實現函式 HuffmanCoding() 函式作用:根據 Huffman 樹生成個字元的 Huffman 編碼。 對於 i=1,...n,執行以下過程:

1)採用"自下而上"(即自葉至根)方式為陣列 ht[]中的當前葉結點 ht[i]產生 Huffman 編碼,所得編碼臨時存放在 cd[start..n-1]中。顯然,該編碼的長度為(n-1)-start+1=n-start 個字元。

2)動態分配長度為 n-start 個位元組的記憶體,並且令字元指標 hc[i]指向這段空間的首地址。

3)將 cd[start..n-1]的內容複製到 hc[i]所指向的記憶體空間。

引數含義

htHuffmanTree 型別陣列的首地址

hc:根據"typedef char * HuffmanCode"可知,引數 hc 的型別為 char * *,即 hc 可以理

解為一個 char *型別陣列的首地址。其中,單元 hc[0..n-1]中依次存放一個字元指標,且這些 字元指標分別指向所對應的葉結點的 Huffman 編碼。具體地,hc[0]指向第一個葉結點的 Huffman 編碼,hc[1]指向第一個葉結點的 Huffman 編碼,以此類推。 函式內部變含義

cd:長度為 n 的字元陣列的首地址,其中,cd[n-1]='\0',且 cd[start..n-2]中存放了某個葉

結點的 Huffman 編碼。

void HuffmanCoding(HuffmanTree *ht,int n,HuffmanCode *hc)

{ char *cd; int start,i; int current,parent;

//用來臨時存放一個字元的編碼結果

cd=(char*)malloc(sizeof(char)*n);

cd[n-1]='\0'; //設定字串結束標誌 for(i=1;i<=n;i++)

{ start=n-1; current=i; parent=ht[current].parent;//獲取當前結點的父結點 while(parent) //父結點不為空

{

if(current==ht[parent].left)//若該結點是父結點的左子樹 cd[--start]='0'; //編碼為0

else //若結點是父結點的右子樹

cd[--start]='1'; //編碼為1 current=parent; //設定當前結點指向父結點

parent=ht[parent].parent; //獲取當前結點的父結點序號

}

hc[i-1]=(char*)malloc(sizeof(char)*(n-start));//分配儲存編碼的記憶體 strcpy(hc[i-1],&cd[start]); //複製生成的編碼

}

free(cd); //釋放編碼佔用的記憶體

}

步驟6:編寫主函式 main() int main(int argc, char *argv[])

{

int i,n=4,m; char alphabet[]={'A','B','C','D'}; //4個字元 int w[]={5,7,2,13} ;//4個字元的權重

HuffmanTree *ht;

HuffmanCode *hc;

m=2*n-1; //計算總的結點數量

//申請記憶體,儲存赫夫曼樹。共申請m+1個單元,其中ht[0]空閒不用。 ht=(HuffmanTree *)malloc((m+1)*sizeof(HuffmanTree)); if(!ht) { printf("記憶體分配失敗!\n"); exit(0); }

/*申請記憶體,建立長度為n的字元指標陣列,其中元素hc[i-1]指向第i個葉結點的

Huffman編碼*/

hc=(HuffmanCode *)malloc(n*sizeof(char*)); if(!hc) { printf("記憶體分配失敗!\n"); exit(0);

}

CreateTree(ht,n,w); //利用n個葉結點和相關權重建立赫夫曼樹 HuffmanCoding(ht,n,hc); //為每個葉結點(即字元)產生對應的赫夫曼編碼 for(i=1;i<=n;i++) //輸出每個葉結點(即字元)的赫夫曼編碼

printf("字母:%c,權重:%d,編碼為 %s\n",alphabet[i-1],ht[i].weight,hc[i-1]); system("PAUSE"); return 0;

}

執行效果