數據結構回顧(八) 基於哈夫曼樹的壓縮編碼實現 (C/C++)

構建哈夫曼樹的核心思想便是貪心,但在實現細節上卻可以有許多種寫法。記得當時研究的問題是基於哈夫曼樹的文件壓縮,在此實現了核心組件,也就是哈夫曼樹的構建生成。
以字符編碼爲例:在此先對字符集進行統計,得到每個字符的權重。而後對每個字符的字符值以及權重進行封裝,以此得到相應數據節點(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;

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章