C++實現霍夫曼編碼
阿新 • • 發佈:2019-02-10
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; }
程式圖: