上週日做了數據結構實驗報告,感覺還行。
一、課題描述
編制一個運用哈夫曼編碼的相關知識對任意文本文件進行編碼、解碼,並保存相關結果的程序。
二、概要設計(主要思想應根據代碼執行順序瞭解)
1)數據邏輯結構:主要是樹形結構,也有使用線性結構作爲輔助;
存儲結構分析:主要是非線性結構(二叉樹)。
結點:struct HuffTree{
char data;
int weight;
HuffTree*left=NULL, *right=NULL;
};
2)本程序包含8個函數:
(1)將數據樹化函數HuffTree* create(const char c, int i)
c爲字符,i爲權值,將其存入一棵最小的二叉樹中,左右兒子均爲空,然後返回這棵二叉樹,配合init()函數使用。
(2)初始化函數void init():
接受輸入的字符及其權值,並將每一組字符和權值樹化,存入vector<HuffTree>容器Huff中。
(3)比較函數bool cmp(HuffTree*h1,HuffTree*h2)
比較兩個結點,如果前者權值小於後者,返回TRUE,否則返回FALSE,輔助sort函數使用。
(4)合併兩棵樹函數HuffTree* merge(HuffTree*t1, HuffTree*t2)
創建一個新結點,權值爲t1和t2的權值之和,char數據爲’#’,表示非輸入的字符,然後返回這個結點(樹)。
(5)(主要算法實現)編碼函數HuffTree* Encode()
在容器Huff裏面,只要不止一棵樹,就進行排序,將權值小的排在前面,按規則合併最小的兩棵樹(刪除原兩樹,將新樹插入),當還剩下一棵樹的時候,這棵樹就是哈夫曼樹,然後把這棵樹返回,用FinalTree接收。
(6)得到編碼函數void HuffVisit(HuffTree*t, string s)
遍歷FinalTree,以左爲1,右爲0,每碰到data爲字母的子樹,就把當前的01字符和字母串存入容器HuffDic中(以字母爲key)
(7)譯碼函數void Decode(HuffTree*t, string s)
依s中01字符串以1左0右的規則遍歷FinalTree,得到的每一個字母均存入string decStr中,得到譯碼字符串decStr。
(8)文件操作函數void file()
將HuffDic和decStr中存儲的結果寫入文件中。
(9)展示函數void show()
將測試結果打印出來
(10)主函數int main()
調用其他函數,完成功能。
三、代碼編寫(注:本代碼在Visual Studio 2017下編譯運行通過)
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
#include<cctype>
#include<fstream>
using namespace std;
struct HuffTree //樹結點結構體
{
char data; //數據:字符
int weight; //權值
HuffTree*left = NULL, *right = NULL; //左右子樹
};
typedef pair<char, string> Pair; //爲了方便起見,現將前者簡化寫法
bool space=false;
HuffTree* FinalTree; //最終的哈夫曼樹
vector<Pair> HuffDic; //存放每個字符的哈夫曼編碼
vector<HuffTree*> Huff; //存放開始的全部哈夫曼樹
string DecodeStr; //對01字符串解碼後的字符串
HuffTree* CreateTree(const char c, int i) //將數據化爲小樹
{
HuffTree*t = new HuffTree;
t->data = c;
t->weight = i;
return t; //返回小樹
}
void Initial()
{
int n, t;
char c;
cout << "請輸入數據組數:"; //數據組數:欲編碼字母個數
cin >> n;
while (n--)
{
static int k = 1;
cout << "第" << k++ << "組(字母,權值):";
cin >> c >> t;
Huff.push_back(CreateTree(c, t)); //將小樹存Huff中去
}
cout << "是否輸入空格' '的權值?如果是,請輸入正整數;如果否,請輸入負數:"; //確認是否爲空格編碼
cin >> t;
if (t >= 0)
{
Huff.push_back(CreateTree(' ', t));
space = true;
}
}
bool SortCmp(HuffTree*h1, HuffTree*h2) //sort的比較函數重載
{
return h1->weight < h2->weight; //按權值升序
}
HuffTree* TreeMerge(HuffTree*t1, HuffTree*t2) //和並兩棵小樹,使兩棵小樹成爲大樹的孩子
{
HuffTree*t = new HuffTree;
t->data = '#'; //以#號表示無字符
t->weight = t1->weight + t2->weight; //大樹的權值是兩棵小樹權值之和
t->left = t1;
t->right = t2;
return t;
}
HuffTree* GetFinalTree()
{
//for (int i = 0; i < Huff.size(); i++)
//{
//cout << Huff[i]->weight << ' ' << Huff[i]->data << endl;
//}
while (Huff.size() > 1) //Huff內不止一棵樹的時候
{
sort(Huff.begin(), Huff.end(), SortCmp); //把最小的樹排序到前面
HuffTree*t1, *t2;
t1 = Huff.front();
Huff.erase(Huff.begin());
t2 = Huff.front();
Huff.erase(Huff.begin());
Huff.push_back(TreeMerge(t1, t2)); //合併最小的兩棵樹
}
return Huff.front(); //返回最後剩餘的一棵樹,即FinalTree
}
void Encode(HuffTree*t, string s)
{
//if (isalpha(t->data))
if (t->data != '#')
HuffDic.push_back(Pair(t->data, s)); //如果字符不是#,則和相應的01字符串一起存入HuffDic中
if (t->left) //如果有左子樹
{
string sl = s;
sl.push_back('0'); //左邊爲1,放入string
Encode(t->left, sl); //遞歸
}
if (t->right) //如果有右子樹
{
string sr = s;
sr.push_back('1'); //右邊爲0,放入string
Encode(t->right, sr); //遞歸
}
}
void Decode(HuffTree*t, string s)
{
if (t->left == NULL || s.empty()) //當沒有孩子或01字符串完了的時候
{
DecodeStr.push_back(t->data); //爲相應的字母,取出放到DecodeStr中
if (!s.empty())
Decode(FinalTree, s); //如果s還不爲空的話,繼續下一個字符的解碼
}
else
{
char front = s.front(); //有孩子且還有01字符串時,去掉首字符,繼續遍歷
s.erase(s.begin());
//首字符爲0向左,首字符爲1向右
Decode((front == '0' ? t->left : t->right), s);
}
}
void FileAction()
{
fstream f("D:\\test.txt", ios::out); //打開文件,若不存在便創建
if (!f)
{
cout << "FileError!! Can't read and write file 'test.txt'!!"; //打不開文件即退出函數
return;
}
for (int i = 0; i < HuffDic.size(); i++)
{
f << HuffDic[i].first << "——" << HuffDic[i].second << endl; //將HuffDic中保存的字母及其編碼寫入文件
}
f << DecodeStr; //將DecodeStr寫入文件
f.close();
cout << "結果已保存至——'test.txt'!" << endl;
}
void Results()
{
sort(HuffDic.begin(), HuffDic.end()); //將小的字符排在前,進行打印
cout << "哈夫曼編碼:" << endl;
for (int i = 0; i < HuffDic.size(); i++)
{
cout << HuffDic[i].first << "——" << HuffDic[i].second << endl; //輸出格式
}
cout << "請輸入欲編碼的字符串:";
//cin >> test;
char c[100] = { 0 };
getchar();
cin.getline(c, 100); //getline以輸入帶空格的字符串
string test(c, c + 100);
int sym = 0;
for (string::iterator it = test.begin(); it != test.end(); it++)
{
sym = 0;
for (vector<Pair>::iterator i = HuffDic.begin(); i != HuffDic.end()&!sym; i++)
if (i->first == *it) sym = 1; //在HuffDic中是否有對應的編碼
if (sym) //如果有
{
if (*it == ' ')
cout << HuffDic.front().second; //如果是空格
else if (*it)
cout << HuffDic[*it - 65 + int(space)].second; //輸出測試數據中每個字母的哈夫曼編碼
}
//else
//{
// cout << "字符串中有未編碼字符,編碼停止,你可以檢查並再次運行.";
// system("pause");
// exit(0);
//}
}
if (sym)
cout << "字符串中有未編碼字符,編碼結果可能錯誤";
cout << endl << "請輸入欲解碼的01編碼:"<<endl;
cin >> test;
Decode(FinalTree, test); //輸入編碼,進行解碼
cout << DecodeStr << endl;
}
int main()
{
Initial();
FinalTree = GetFinalTree();
Encode(FinalTree, "");
Results();
FileAction();
return 0;
}