1. 程式人生 > >C++實現霍夫曼編碼

C++實現霍夫曼編碼

Huffman類宣告與實現:

Huffman.h

#ifndef HUFFMAN_H
#define HUFFMAN_H
#include<vector>
#include<string>
#include<sstream>
struct Huffman_Node;
class Huffman;
struct Huffman_Node
{
	Huffman_Node() = default;
	Huffman_Node(int w, char W, Huffman_Node *D, Huffman_Node *L,Huffman_Node *R,bool use = false)
	{
		weight = w;
		Word = W;
		Parent = D;
		Left = L;
		Right = R;
		Used = use;
	};
	~Huffman_Node() { };

	int weight;//權值,字元,父節點指標,左右孩子指標。
	char Word;
	Huffman_Node *Parent;
	Huffman_Node *Left;
	Huffman_Node *Right;
	bool Used;
 
};
class Huffman
{
public:
	Huffman() = default;
	Huffman(std::ifstream &In);//構造霍夫曼樹。
	~Huffman() { };

	void Codeing();//編碼。
	void Translating();//解碼。
	void PrintCode();//在顯示器上列印編碼並格式化的寫入檔案。
	void PrintTree();//列印構建好的霍夫曼樹。

private:
	std::vector<Huffman_Node*> Huf;//儲存霍夫曼樹節點。
	Huffman_Node *root;
	void print(Huffman_Node* p, std::string s,std::ofstream &Write);//列印霍夫曼樹。
};
#endif // !HUFFMAN_H

Huffman.c

#include"Huffman.h"
#include<algorithm>
#include<string>
#include<vector>
#include<stack>
#include<fstream>
#include<sstream>
#include<iostream>
#include<stdlib.h>
bool Less(int &a, int &b);//用於對vector排序的可呼叫函式。
bool Less(int &a, int &b)//小於。
{
	return (a < b) ? true : false;
}
Huffman::Huffman(std::ifstream &In)//建構函式,初始化資料並構建霍夫曼樹。
{
	if (!In.is_open())
	{
		std::cerr << "無法開啟檔案!" << std::endl;
		return;
	}

	while (!In.eof())
	{
		std::string Line;
		std::getline(In, Line);//從檔案中取得一行。
		if (Line.empty())
			continue;
		std::string Word;//檔案中的字元。
		std::string Weight;//檔案中對應該字元的權值。


		std::istringstream Input(Line);//string流,分離資料。
		Input >> Word;//先提取字元,再提取權值。
		Input >> Weight;


		auto T_n = [](std::string s)->int {return atoi(s.c_str()); };//Lambad,得到int型權值。
		auto T_c = [](std::string ss)->char {return ss[0]; };//Lambad,從string轉化為char。
		Huffman_Node *Node = new Huffman_Node(T_n(Weight), T_c(Word), NULL, NULL, NULL);

		Huf.push_back(Node);//儲存。
	}


	In.close();//檔案讀取結束,關閉!
			   //構建霍夫曼樹。
	std::vector<int> WeightOfHuf;//獲取霍夫曼樹節點的權值。
	for (auto X : Huf)
		WeightOfHuf.push_back(X->weight);

	std::sort(WeightOfHuf.begin(), WeightOfHuf.end(), Less);//呼叫標準庫函式,對權值容器內容排序。

	while (WeightOfHuf.size() != 1)
	{
		Huffman_Node *Deal_1 = NULL, *Deal_2 = NULL;//用來處理各個節點。
		for (auto Find_1 : Huf)//找到第一個最小節點。
			if (Find_1->weight == WeightOfHuf[0] && Find_1->Used == false)
			{
				Find_1->Used = true;//改為已被使用過。
				Deal_1 = &(*Find_1);
				break;
			}
		for (auto Find_2 : Huf)
			if (Find_2->weight == WeightOfHuf[1] && Find_2->Used == false)//找到第二小的節點。
			{
				Find_2->Used = true;
				Deal_2 = &(*Find_2);
				break;
			}
		//構建新節點,權值為兩項之和,字元為#,並連線左右孩子節點。
		Huffman_Node *NewNode = new Huffman_Node(Deal_1->weight + Deal_2->weight, '#', NULL, &(*Deal_1), &(*Deal_2));
		Deal_1->Parent = &(*NewNode);//將孩子節點的父節點指標連起來。
		Deal_2->Parent = &(*NewNode);

		//新節點權值儲存在權值容器中並新增如節點容器。
		WeightOfHuf.push_back(NewNode->weight);
		Huf.push_back(NewNode);
		//刪除已經處理過的兩項權值。
		auto It = WeightOfHuf.end() - 1;//將指標定位在容器末尾。

		WeightOfHuf[0] = *It;
		WeightOfHuf[1] = *(It - 1);
		WeightOfHuf.pop_back();//刪除兩個元素。
		WeightOfHuf.pop_back();

		std::sort(WeightOfHuf.begin(), WeightOfHuf.end(), Less);//重排權值容器

	}
	//root指標指向霍夫曼樹根節點。
	for (auto X : Huf)
		if (X->weight == WeightOfHuf[0])
		{
			root = &((*X));
			break;
		}

}

void Huffman::Codeing()//編碼。
{
	std::cout << "輸入想要編碼的檔案地址!" << std::endl;
	std::string ReadCode_Path;
	std::getline(std::cin, ReadCode_Path);//獲取編碼檔案地址。

	std::ifstream In_Code(ReadCode_Path);//定義一個讀檔案流,讀取資訊。
	if (!In_Code.is_open())//判斷檔案是否開啟正常。
	{
		std::cerr << "無法代開檔案,檔案地址錯誤!" << std::endl;
		return;
	}

	std::cout << "輸入編碼資料儲存檔案地址!" << std::endl;
	std::string WriteCode_Path;
	std::getline(std::cin, WriteCode_Path);//獲取編碼儲存檔案地址。

	while (!In_Code.eof())
	{
		std::string Line;//一行原資料。
		std::string Trans_Line;//編碼結果。
		std::getline(In_Code, Line);//讀取一行處理。
		if (Line.empty())
			continue;
		for (int i = 0; i < Line.length(); ++i)//對一行中的每一個字元進行編碼。
		{
			Huffman_Node *Current_Node = NULL;
			if (Line[i] == ' ')//排除空格。
				continue;
			else
			{
				for (auto X : Huf)//找到該字元對應的霍夫曼節點。
					if (X->Word == Line[i])
					{
						Current_Node = &(*X);
						break;
					}
			}
			std::stack<std::string> Code;//棧用來儲存編碼。
			Code.push(" ");//先追加一個空格,方便格式化輸出。

						   //從找到的節點開始回溯至根節點,得到一個字元的編碼。
			while (Current_Node != root)
			{
				if (Current_Node->Parent != NULL && Current_Node == Current_Node->Parent->Left)//是父節點的左孩子節點。
				{
					Code.push("0");//左節點應為編碼0.
					Current_Node = Current_Node->Parent;//指標移向父節點。
				}
				if (Current_Node->Parent != NULL && Current_Node == Current_Node->Parent->Right)//是父節點的右孩子節點。
				{
					Code.push("1");//右節點應為編碼1.
					Current_Node = Current_Node->Parent;//指標移向父節點。
				}
			}
			while (!Code.empty())//得到真正的編碼。
			{
				Trans_Line.append(Code.top());
				Code.pop();
			}
		}
		//一行處理完畢,寫入目標檔案。
		std::ofstream Out_Code(WriteCode_Path, std::ofstream::out | std::ofstream::app);//以代開檔案每次定位在檔案尾的形式建立寫檔案流。
		Out_Code << Trans_Line << std::endl;//寫入一行。
	}
}

std::ofstream& operator<<(std::ofstream& Out, std::vector<char> Vec)//過載運算子<<,使後邊譯碼內容得以方便寫入檔案。
{
	for (auto X : Vec)
		Out << X;
	return Out;
}

void Huffman::Translating()//譯碼。
{
	std::cout << "輸入需要譯碼的檔案地址!" << std::endl;
	std::string Trans_Path;
	std::getline(std::cin, Trans_Path);

	std::ifstream ReadTrans(Trans_Path);//讀取譯碼檔案內容。

	std::cout << "輸入譯碼後結果儲存檔案地址!" << std::endl;
	std::string TransSave_Path;
	std::getline(std::cin, TransSave_Path);

	std::ofstream WriteTrans(TransSave_Path, std::ofstream::out | std::ofstream::app);//寫檔案流。

	while (!ReadTrans.eof())
	{
		std::string Line;
		std::getline(ReadTrans, Line);
		if (Line.empty())
			continue;
		std::istringstream DealLines(Line);//string流,處理每一段編碼。
		std::string Deal;

		std::vector<char> Trans_Inf;//儲存一行譯碼結果。
		while (DealLines >> Deal)//得到一段編碼並處理。
		{
			Huffman_Node *Current_Node = root;//從根節點開始。
			for (int i = 0; i < Deal.length(); ++i)//根據編碼找到字元。
			{
				switch (Deal[i])
				{
				case '1'://編碼為1,右孩子節點。
				{
					Current_Node = Current_Node->Right;//更改指標。
					break;
				}
				case '0'://編碼為0,左孩子節點。
				{
					Current_Node = Current_Node->Left;
					break;
				}
				default:
					break;
				}
			}
			Trans_Inf.push_back(Current_Node->Word);//得到編碼對應的字元。

		}
		WriteTrans << Trans_Inf << std::endl;//向檔案寫入譯完的一行。
	}
}

void Huffman::PrintCode()//列印在終端上。
{
	//將codefile檔案以緊湊格式顯示在終端上。
	std::cout << "輸要在終端上顯示內容的檔案的地址!" << std::endl;
	std::string Print_Path;
	std::getline(std::cin, Print_Path);

	std::ifstream ReadPrint(Print_Path);//讀檔案內容。

	std::string Show;
	while (!ReadPrint.eof())//先將檔案內容全部拷貝。
	{
		std::string word;
		std::getline(ReadPrint, word);
		std::string SubstrOfShow;
		std::istringstream DealStr(word);//繫結string流。
		while (DealStr >> SubstrOfShow)
			Show.append(SubstrOfShow);
	}
	for (int i = 0; i < Show.length(); ++i)//每50個輸出。
	{
		if (i % 50 == 0 && i != 0)//50個打一行。
			std::cout << std::endl;
		else
			std::cout << Show[i];
	}
	std::cout << std::endl;
	//將次形式的內容寫在檔案codeprint中。

	std::cout << "輸入想以在終端上顯示的內容為樣式儲存資料的檔案地址!" << std::endl;

	std::string Print;
	std::getline(std::cin, Print);

	std::ofstream WritePrint(Print, std::ofstream::out | std::ofstream::app);//寫檔案。
	for (int i = 0; i < Show.length(); ++i)
	{
		if (i % 50 == 0)//每50個編碼為一行。
			WritePrint << std::endl;
		else
			WritePrint << Show[i];
	}
}
void Huffman::print(Huffman_Node *p, std::string s, std::ofstream &Write)
{
	if (p == NULL)
		return;
	s += "      ";
	print(p->Right, s, Write);//先打右子樹。
	std::cout << s << p->Word << std::endl;
	Write << s << p->Word << std::endl;//後打左子樹。
	print(p->Left, s, Write);

}
void Huffman::PrintTree()//以凹入表的形式列印構建好的霍夫曼樹。
{
	std::string s = "";

	std::cout << "輸入霍夫曼樹凹入表儲存的檔案地址!" << std::endl;
	std::string Tree_File;
	std::getline(std::cin, Tree_File);

	std::ofstream Write_Tree(Tree_File, std::ofstream::out | std::ofstream::app);//寫檔案流。
	if (!Write_Tree.is_open())
	{
		std::cerr << "檔案路徑錯誤!" << std::endl;
		return;
	}

	print(root, s, Write_Tree);

}


原始檔:
#include"Huffman.h"
#include<vector>
#include<iostream>
#include<fstream>
#include<map>
using namespace std;
ofstream &operator<<(ofstream &Write, map<char, int> M);//過載map的<<操作符,方便寫入檔案。
int main()
{
	//從鍵盤輸入字元和權值,儲存在檔案中。
	//從檔案讀取內容,初始化霍夫曼樹。
	cout << "**********                  霍夫曼編碼/解碼器                **********" << endl;
	cout << "**********--1.輸入數字 1 直接從終端輸入要解碼的字元的權值!  **********" << endl;
	cout << "**********--2.輸入數字 2 從給定的文章中統計字元權值,再編碼!**********" << endl;
	cout << "**********--3 輸入數字 3 從存放權值資訊的檔案編碼!          **********" << endl;
	while (true)
	{
		int chose;
		cin >> chose;
		if (!cin.fail())
		{
			if(chose == 1 || chose == 2 || chose == 3)
				switch (chose)
			{
			case 1:
			{
				int N;
				cout << "輸入字符集N的大小!" << endl;
				cin >> N;
				vector<string> VEc;
				for (int i = 0; i <= N; ++i)//收集字元權值。
				{
					string Get;
					if (i == 0)
					{
						getline(cin, Get);
						continue;
					}
					cout << "輸入一個字元及其權值" << endl;
					getline(cin, Get);
					VEc.push_back(Get);
				}

				cout << "輸入權值內容要存放的檔案的地址!" << endl;
				string InfPath;
			
				getline(cin, InfPath);
				ofstream In(InfPath, ostream::out | ostream::app);
				for (auto S : VEc)
				{
					In << S << endl;
				}
				ifstream Get(InfPath);//獲取權值檔案。
									  //例項化物件!
				Huffman HufTree(Get);

				HufTree.Codeing();//編碼。
			    HufTree.Translating();//解碼。
				HufTree.PrintCode();//列印編碼。

				HufTree.PrintTree();//列印構建的樹。
				break;
			}
			case 2:
			{
				cout << "輸入要編碼的檔案地址!" << endl;
				string text_Path;
				string Else;
				getline(cin, Else);
				getline(cin, text_Path);//輸入編碼檔案地址。

				ifstream Read_Inf(text_Path);//讀取檔案內容。

				map<char, int> Weight_Cal;//統計權值匹配容器。
				while (!Read_Inf.eof())
				{
					string line;
					getline(Read_Inf, line);

					istringstream Do(line);
					for (int i = 0; i < line.length(); ++i)
						if (Weight_Cal.find(line[i]) == Weight_Cal.end())//沒找到。新增。
							Weight_Cal[line[i]] = 1;
						else
							Weight_Cal.find(line[i])->second += 1;//找到,數目加一。
				}
				cout << "輸入儲存權值檔案的地址!" << endl;
				string path;
				getline(cin, path);

				ofstream Write_Weight(path, ofstream::out | ofstream::app);
				if (!Write_Weight.is_open())
				{
					cout << "檔案地址錯誤!" << endl;
					return -1;
				}
				Write_Weight << Weight_Cal;//權值統計資料寫入檔案。

				ifstream Get(path);//獲取權值檔案。
								   //例項化物件!
				Huffman HufTree(Get);

				HufTree.Codeing();//編碼。
				HufTree.Translating();//解碼。
				HufTree.PrintCode();//列印編碼。

				HufTree.PrintTree();//列印構建的樹。
				break;
			}
			case 3:
			{
				string path;
				cout << "輸入權值檔案地址!" << endl;
				string Else;
				getline(cin, Else);
				getline(cin, path);
				ifstream Get(path);//獲取權值檔案。
								   //例項化物件!
				Huffman HufTree(Get);

				HufTree.Codeing();//編碼。


				HufTree.Translating();//解碼。
				HufTree.PrintCode();//列印編碼。

				HufTree.PrintTree();//列印構建的樹。
				break;
			}
			default:
				break;
			}
			else
			{
				cout << "請做出正確選擇!" << endl;
				continue;
			}
			break;
		}
		else
		{
			cin.sync();
			cin.clear();
			cout << "    請做出正確選擇!" << endl;
		}
	}
	system("pause");
}
ofstream &operator<<(ofstream &Write, map<char, int> M)//過載map的<<操作符,方便寫入檔案。
{
	for (auto It = M.begin(); It != M.end(); ++It)
		Write << It->first << " " << It->second << endl;
	return Write;
}


程式圖: