構建哈夫曼樹的核心思想便是貪心,但在實現細節上卻可以有許多種寫法。記得當時研究的問題是基於哈夫曼樹的文件壓縮,在此實現了核心組件,也就是哈夫曼樹的構建生成。
以字符編碼爲例:在此先對字符集進行統計,得到每個字符的權重。而後對每個字符的字符值以及權重進行封裝,以此得到相應數據節點(Node)。將這些節點構建成以權重爲參考的有序鏈表,每次從鏈表上取下兩個權重最大的節點進行生成樹構建,得到生成樹的root節點鏈接回鏈表中,當然在將root節點插回到鏈表後還要保證其有序;相當於維護了一個優先級隊列。循環合併節點,最後在隊列中只剩下一個節點;便是哈夫曼樹的根節點了。自此哈夫曼樹的生成完成,關於這個思想如下圖所示:(還是近三年前讀大二時同學問到的問題,在當天熬夜到12點(12點對於我來說已經是熬夜了)完成的,真的不知道以後還會不會有這樣的熱血了。一直惦記着這個demo,真的沒想到還可以找回來,太值得紀念了。)
哈夫曼樹生成後,字符編碼生成就是一個簡單地節點搜尋過程了,其他的一些細節實現見代碼。
#include<iostream>
#include<fstream>
#include<iomanip>
#define Datatype char
using namespace std;
struct Node
{
Node *lchild,*rchild;
Node *parent;
Node *next;
Datatype data;
int weight;
int flag;//記錄當前節點爲做孩子還是右孩子
};
class Hfm
{
private:
Node **leef_record;//記錄各節點 方便哈弗生成編碼
string *hfcode;//編碼
int nums;//統計字符數(鏈表結點個數)
int size;//
Node *head;//頭結點
public:
Hfm()
{
nums=0;
head=new Node; //頭結點預留
head->next =NULL;
}
bool get_data_and_statistic();//從外存得到數據且統計
void show_list();//展示統計結果
bool copy_node();//複製鏈表到指針數組上
void show_copy_record();//展示覆制後的指針數組內容
bool create_hftree();//創建哈弗曼樹 輔助
bool c_h_t();//創建哈弗曼樹 //在此約定min1 <min2 (min1 min2 節點中權值最小的兩個節點)
void swap(Node *p1,Node *p2);//交換兩節點值
bool show_hftree_and_get_hfcode();//遍歷哈弗曼樹得到哈弗編碼
};
bool Hfm::show_hftree_and_get_hfcode()//遍歷哈弗曼樹得到哈弗編碼
{
fstream fs;
fs.open ("c:\\hfcode.txt",ios::out );
if(!fs.is_open() )
{
return false;
}
//cout<<"sizez :"<<size<<endl;
Node *p;
cout<<" hafucode(在C盤文件夾下查看 實際哈弗曼編碼爲以下字符串的逆序以避免前綴碼重複問題)"<<endl;
for(int i=0;i<size;i++)
{
p=this->leef_record [i];
cout<<p->data <<" ";
while(p && p->flag !=-1)
{
fs<<p->flag ;
cout<<p->flag ;
p=p->parent ;
}
cout<<endl;
fs<<" ";
}
fs.close ();
}
void Hfm::swap(Node *p1,Node *p2)//交換兩節點值
{
Node p;
p=*p1;
*p1=*p2;
*p2=p;
}
bool Hfm::create_hftree()//創建哈弗曼樹 輔助
{
if(nums>=2)
{
this->c_h_t ();//在此假設第一個及第二個節點爲最小權 在接下函數中進行一個軸參考
}
else
{
cout<<"僅有一類字符!編碼爲 1 !"<<endl;
return true;
}
}
bool Hfm::c_h_t()//創建哈弗曼樹
{
Node *min1, *min2;
//show_list();
nums--;
//cout<<"nums: "<<nums<<endl;
//cout<<"0"<<endl;
Node *p=this->head->next ;
if(nums>=1)
{
int k=0;
while(p) //選取參考軸
{
if(p->flag ==-1)
{
if(k==0)
{
//cout<<"0000"<<endl;
min1=p;
k=1;
}
else
{
//cout<<"11111"<<endl;
min2=p;
break;
}
}
p=p->next ;
}
//cout<<"1"<<endl;
/*if(min1->weight > min2->weight) // 在此約定min1 <min2
{
swap(min1,min2);
}*/
// cout<<"2"<<endl;
if(nums>1)
{
p=this->head->next ;
while(p) // 選取最小且未處理節點 (在以下算法中若無特別說明所謂最小都是以權權值爲參考系)
{
if(min1->weight > p->weight && p->flag ==-1)
{
min1=p;
}
p=p->next ;
}
min1->flag =0;//min1作爲左孩子
//cout<<"3"<<endl;
p=this->head->next ;
while(p) // 選取次小且未處理節點 (在以下算法中若無特別說明所謂最小都是以權權值爲參考系)
{
if(min2->weight >= p->weight && p->flag ==-1)
{
min2=p;
}
p=p->next ;
}
min2->flag =1;//min2作爲右孩子
//cout<<"select : min1: "<<min1->data <<" min2: "<<min2->data <<endl;
//cout<<"4"<<endl;
}
}
if(nums>1)
{
//cout<<"5"<<endl;
//錯位掛鏈 + 頭插法 +哈弗曼樹創建
Node *new_node;
new_node=new Node;
new_node->lchild =min1; //掛上樹
new_node->rchild =min2;
new_node->flag=-1;
new_node->weight =min1->weight +min2->weight ; //保存兩節點權值之和
//new_node->data ='*';
min1->parent =new_node;//掛上樹
min2->parent =new_node;
new_node->next =this->head ->next ;//new_node作爲新節點掛入鏈表中
this->head ->next =new_node;
c_h_t();//遞歸
}
else
{
//cout<<"6"<<endl;
this->head ->flag =-1;
this->head ->lchild =min1;
this->head->rchild =min2;
//cout<<" min1->flag "<<min1->flag<<endl;
//cout<<" min2->flag "<<min2->flag<<endl;
min1->flag =0;
min2->flag =1;
//cout<<" min1->weight "<<min1->weight<<endl;
//cout<<" min2->weight "<<min2->weight<<endl;
this->head->weight =min1->weight +min2->weight ;
min1->parent =this->head ;//掛上樹
min2->parent =this->head ;
head->parent =NULL;
}
return true;
}
void Hfm::show_copy_record()//展示覆制後的指針數組內容
{
cout.setf(ios::left );
cout<<" "<<setw(10)<<"data"<<setw(10)<<"weight"<<endl;
for(int i=0;i<this->size ;i++)
{
cout<<" "<<setw(10)<<this->leef_record [i]->data <<setw(10)<<this->leef_record [i]->weight <<endl;
}
}
bool Hfm::copy_node()//複製鏈表到指針數組上
{
this->leef_record =(Node**)malloc(sizeof(Node*)*this->nums );//開闢空間
int count=0;
Node *p=this->head ->next ;
while(p)//記錄節點指針 便於生成哈弗編碼
{
this->leef_record [count++]=p;
p=p->next ;
}
return true;
}
void Hfm::show_list()//展示統計結果
{
Node *p;
p=this->head->next ;
cout.setf(ios::left );
cout<<" "<<setw(10)<<"data"<<setw(10)<<"weight"<<setw(10)<<"flag"<<endl;
while(p)
{
cout<<" "<<setw(10)<<p->data <<setw(10)<<p->weight <<setw(10)<<p->flag <<endl;
p=p->next ;
}
cout<<"nums: "<<this->nums <<endl;
}
bool Hfm::get_data_and_statistic ()//從外存得到數據
{
fstream fs;
fs.open ("c:\\hfm.txt",ios::in);
if(!fs.is_open ())
{
return false;
}
else
{
char ch;
Node *p,*p1;
while((ch=fs.get())!=EOF)
{
p1=head->next ;
while(p1)//若在節點中已存在該字符則直接進行自加即可
{
if(p1->data ==ch)
{
p1->weight ++;
break;
}
p1=p1->next ;
}
if(!p1)//若在節點中不存在該字符則創建一個節點且掛鏈
{
p=new Node;
p->lchild =NULL;
p->rchild =NULL;
p->parent=NULL;
p->flag =-1; //-1表示未處理
p->weight=1;//表該字符當前出現一次
p->data =ch;//賦值
p->next =head->next ;//掛鏈 頭插法
head->next =p;
nums++;//節點數自加
}
}
head->next=p->next;//文件讀取錯位處理
free(p);
nums--;
this->size =nums;
}
}
int main()
{
Hfm hf;
hf.get_data_and_statistic ();
cout<<"字符統計如下:"<<endl;
hf.show_list ();
hf.copy_node ();
//hf.show_copy_record ();
hf.create_hftree();
hf.show_hftree_and_get_hfcode();
return 0;
}