❤️️💚💙💛🧡💜🖤🤍🧡
大家好!我是曾续缘🥰
欢迎关注💕
❤️点赞 👍 收藏 ⭐再看,养成习惯
🔥考虑一千次,不如去做一次;犹豫一万次,不如实践一次;华丽的跌倒,胜过无谓的彷徨,将来的你,一定会感谢现在奋斗的你。📚
哈夫曼树,也称最优二叉树、带权路径长度最短的二叉树
一、哈夫曼树的基本概念
路径:从树中一个结点到另一个结点之间的分支构成这两个结点间的路径
结点的路径长度:两结点之间路径上的分支数
树的路径长度:从树根到每一个结点的路径长度之和
权:给树中结点赋一个数值,这个数值称为该结点的权
结点的带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积.
树的带权路径长度:树中所有叶子结点的带权路径长度之和.记作:WPL(Weighted Path Length)
二、哈夫曼树的特点
1、满二叉树不一定是哈夫曼树.
2、哈夫曼树中权越大的叶子离根越近.
3、具有相同带权结点的哈夫曼树不惟一.
4、只有度为0或2的结点,没有度为1的结点
5、包含n个叶子结点的哈夫曼树中共有2n-1个结点
三、哈夫曼树的结构
#include<iostream>
#include<fstream>
#include<string>
#define leafNumber 100
#define totalNumber 200
using namespace std;
template<class T, class E>
struct HFNode {
T key; // 权重域
E data; // 数据域,保存字符信息
char code[20]; // 哈夫曼编码(叶子结点具有)使用数组有助于文件读写
int lchild, rchild, parent;
};
template<class T, class E>
struct HFTree {
HFNode<T, E> elem[totalNumber]; // 哈夫曼结点数组
int num; // 叶子结点个数
int root; // 根结点
HFTree() {
num = 0;root = 0;
for (int i = 0;i < totalNumber;i++) {
for (int j = 0;j < 20;j++)elem[i].code[j] = '\0';
}
}
void createHFTree(E ch[], T fr[], int n);
void preintHFTree();
void Initialization();
void Encoding();
void Decoding();
};
四、哈夫曼树的构建
- 初始时,有n个带权结点,构成n棵二叉树的森林,以下操作使用森林的说法,规定森林的权值为其左右子树上根结点的权值之和
- 在森林中,选取两棵根结点的权值最小的树作为左右子树,构造一棵新的二叉树,并设置新的二叉树的根结点的权值为其左右子树上根结点的权值之和.
- 在森林中删除这两棵树,同时将新得到的二叉树加入到森林中.
- 重复(2)和(3),直到森林中只有一棵树为止,这棵树即为哈夫曼树.
代码实现
template<class T, class E>
void HFTree<T, E>::createHFTree(E ch[], T fr[], int n) {
int i, k, s1, s2;
int min1, min2;
for (i = 0;i < n;i++) {
elem[i].data = ch[i]; // 数据
elem[i].key = fr[i]; // 权重
for (int j = 0;j < 20;j++)elem[i].code[j] = '\0'; // 编码初始化为 '\0'
}
for (int i = 0;i < leafNumber;i++) {
elem[i].parent = elem[i].lchild = elem[i].rchild = -1; // 初始化为 -1
}
for (int i = n;i < 2 * n - 1;i++) { // 前n个为叶子结点,具有哈夫曼编码
min1 = min2 = 1e9 + 7;
s1 = s2 = 0;
for (k = 0;k < i;k++) {
if (elem[k].parent == -1) {
if (elem[k].key < min1) {
min2 = min1;
s2 = s1;
min1 = elem[k].key;
s1 = k;
} else if (elem[k].key < min2) {
min2 = elem[k].key;
s2 = k;
}
}
}
elem[s1].parent = elem[s2].parent = i;
elem[i].lchild = s1;
elem[i].rchild = s2;
elem[i].key = elem[s1].key + elem[s2].key;
}
num = n;
root = 2 * n - 2;
}
五、哈夫曼编码
在哈夫曼树的每个分支上标上0或1:结点的左分支标0,右分支标1.把从根到每个叶子的路径上的标号连接起来,作为该叶子代表的字符的编码.
代码实现: 从叶子结点自底向上判断该叶子结点是双亲的左孩子还是右孩子,如果是左孩子,记录0,如果是右孩子,记录1,直到回溯到根结点,翻转这些组合起来的0和1就是哈夫曼的编码
template<class T, class E>
void HFTree<T, E>::createHFTree(E ch[], T fr[], int n) {
// 省略以上哈夫曼树的构建
for (int i = 0;i < num;i++) { // 从叶子结点自底向上判断该叶子结点是双亲的左孩子还是右孩子,如果是左孩子,记录0,如果是右孩子,记录1,直到回溯到根结点,这些组合起来的0和1就是哈夫曼的编码
string code = "";
int j = i;
int parent;
while (elem[j].parent != -1) {
parent = elem[j].parent;
if (j == elem[parent].lchild) {
code += "0";
} else {
code += "1";
}
j = parent;
}
reverse(code.begin(), code.end());// 翻转
int siz = code.size();
for (int j = 0;j < siz;j++) {
elem[i].code[j] = code[j];
}
elem[i].code[siz] = '\0';
cout << "字符" << elem[i].data << "编码为:" << code << "\n";
}
}
哈夫曼编码能够保证是前缀码,即每个编码都不是另一个编码的前缀
六、哈夫曼译码
由于哈夫曼结点已经保存字符信息及其对应的哈夫曼编码,则对于特定的文本内容,只需要将每个字符转换为哈夫曼编码保存下来即可。
代码实现:
template<class T, class E>
void HFTree<T, E>::Decoding() {
cout << "===========================\n";
cout << "=========Decoding==========\n";
string decode = "";
string code = "";
string word = "";
E ch;
cout << "对EncodedContext.txt文件内容进行解码...\n";
ifstream infile("EncodedContext.txt");
if (infile.fail()) {
cout << "文件打开失败!\n";
return;
}
int siz = 0;
while (infile.get(ch)) {
code += ch;
siz++;
}
cout << "需要解码的内容为: " << code << "\n";
int i = 0, j = 0;
for (i = 0;i < siz;i++) {
word += code[i];
for (j = 0;j < num;j++) {
int k = 0;
string hfcode = "";
while (elem[j].code[k] != '\0')hfcode += elem[j].code[k++];
if (hfcode == word) {
decode += elem[j].data;
word = "";
break;
}
}
}
if (i == siz && j == num) {
cout << "解码失败!\n";
return;
}
ofstream outfile("DecodedContext.txt");
outfile << decode;
outfile.close();
cout << "解码成功!解码结果为: " << decode << "\n";
cout << "已保存到DecodedContext中\n";
}
七、哈夫曼树打印
代码实现:
template<class T, class E>
void HFTree<T, E>::preintHFTree() {
cout << "===========================\n";
cout << "========PreintHFTree=======\n";
cout << " ① ② ③ ④ ⑤ ⑥ ⑦ |①节点编号 ②编码字符 ③编码权重 ④左孩子 ⑤右孩子 ⑥双亲 ⑦哈夫曼编码 \n";
for (int i = 0;i < 2 * num - 1;i++) {
E value = (i < num) ? elem[i].data : '-';
if (i < 10)cout << " ";
cout << i << ": " << value;
if (elem[i].key < 10)cout << " ";
cout << " " << elem[i].key;
if (i < num) {
if (elem[i].key < 100)cout << " ";
cout << " " << "*" << " " << "*" << " " << elem[i].parent << " ";
int j = 0;
while (elem[i].code[j] != '\0') {
cout << elem[i].code[j++];
}
cout << endl;
} else {
cout << " " << elem[i].lchild << " " << elem[i].rchild << " " << elem[i].parent << endl;;
}
}
cout << "root=" << root << endl;
}
哈夫曼树的完整代码实现
#include<iostream>
#include<fstream>
#include<string>
#define leafNumber 100
#define totalNumber 200
using namespace std;
template<class T, class E>
struct HFNode {
T key; // 权重
E data; // 数据
char code[20]; // 哈夫曼编码(叶子结点具有)
int lchild, rchild, parent;
};
template<class T, class E>
struct HFTree {
HFNode<T, E> elem[totalNumber]; // 哈夫曼结点数组
int num; // 叶子结点个数
int root; // 根结点
HFTree() {
num = 0;root = 0;
for (int i = 0;i < totalNumber;i++) {
for (int j = 0;j < 20;j++)elem[i].code[j] = '\0';
}
}
void createHFTree(E ch[], T fr[], int n);
void preintHFTree();
void Initialization();
void Encoding();
void Decoding();
};
template<class T, class E>
void HFTree<T, E>::createHFTree(E ch[], T fr[], int n) {
int i, k, s1, s2;
int min1, min2;
for (i = 0;i < n;i++) {
elem[i].data = ch[i]; // 数据
elem[i].key = fr[i]; // 权重
for (int j = 0;j < 20;j++)elem[i].code[j] = '\0'; // 编码初始化为 '\0'
}
for (int i = 0;i < leafNumber;i++) {
elem[i].parent = elem[i].lchild = elem[i].rchild = -1; // 初始化为 -1
}
for (int i = n;i < 2 * n - 1;i++) { // 前n个为叶子结点,具有哈夫曼编码
min1 = min2 = 1e9 + 7;
s1 = s2 = 0;
for (k = 0;k < i;k++) {
if (elem[k].parent == -1) {
if (elem[k].key < min1) {
min2 = min1;
s2 = s1;
min1 = elem[k].key;
s1 = k;
} else if (elem[k].key < min2) {
min2 = elem[k].key;
s2 = k;
}
}
}
elem[s1].parent = elem[s2].parent = i;
elem[i].lchild = s1;
elem[i].rchild = s2;
elem[i].key = elem[s1].key + elem[s2].key;
}
num = n;
root = 2 * n - 2;
for (int i = 0;i < num;i++) { // 从叶子结点自底向上判断该叶子结点是双亲的左孩子还是右孩子,如果是左孩子,记录0,如果是右孩子,记录1,直到回溯到根结点,这些组合起来的0和1就是哈夫曼的编码
string code = "";
int j = i;
int parent;
while (elem[j].parent != -1) {
parent = elem[j].parent;
if (j == elem[parent].lchild) {
code += "0";
} else {
code += "1";
}
j = parent;
}
reverse(code.begin(), code.end());
int siz = code.size();
for (int j = 0;j < siz;j++) {
elem[i].code[j] = code[j];
}
elem[i].code[siz] = '\0';
cout << "字符" << elem[i].data << "编码为:" << code << "\n";
}
}
template<class T, class E>
void HFTree<T, E>::preintHFTree() {
cout << "===========================\n";
cout << "========PreintHFTree=======\n";
cout << " ① ② ③ ④ ⑤ ⑥ ⑦ |①节点编号 ②编码字符 ③编码权重 ④左孩子 ⑤右孩子 ⑥双亲 ⑦哈夫曼编码 \n";
for (int i = 0;i < 2 * num - 1;i++) {
E value = (i < num) ? elem[i].data : '-';
if (i < 10)cout << " ";
cout << i << ": " << value;
if (elem[i].key < 10)cout << " ";
cout << " " << elem[i].key;
if (i < num) {
if (elem[i].key < 100)cout << " ";
cout << " " << "*" << " " << "*" << " " << elem[i].parent << " ";
int j = 0;
while (elem[i].code[j] != '\0') {
cout << elem[i].code[j++];
}
cout << endl;
} else {
cout << " " << elem[i].lchild << " " << elem[i].rchild << " " << elem[i].parent << endl;;
}
}
cout << "root=" << root << endl;
}
template<class T, class E>
void HFTree<T, E>::Initialization() {
cout << "===========================\n";
cout << "======Initialization=======\n";
cout << "请输入字符集大小n: ";
int n;
cin >> n;
if (n <= 0)return;
E* ch = new E[n];
T* fr = new T[n];
cout << "请连续输入" << n << "个字符:\n";
string code = "";
cin.ignore();
getline(cin, code);
if (code.size() != n) {
cout << "输入长度为" << code.size() << ",输入错误\n";return;
}
for (int i = 0;i < n;i++) {
ch[i] = code[i];
}
cout << "请依次输入n个权值:\n";
for (int i = 0;i < n;i++) {
cin >> fr[i];
}
createHFTree(ch, fr, n);
cout << "建树完成\n";
cout << "是否替换原来的文件(Y/N): ";
char choice;
cin >> choice;
if (choice == 'Y' || choice == 'y') {
ofstream outfile;
outfile.open("SavedHfmTree.dat", ios::out | ios::binary | ios::trunc);
if (outfile.fail()) {
cout << "文件打开失败\n";return;
}
outfile.write((char*)&num, sizeof num);
outfile.write((char*)&root, sizeof root);
for (int i = 0;i < totalNumber;i++) {
outfile.write((char*)&elem[i], sizeof elem[i]);
}
outfile.close();
cout << "保存成功\n";
}
}
template<class T, class E>
void HFTree<T, E>::Encoding() {
cout << "===========================\n";
cout << "=========Encoding==========\n";
if (num == 0) {
cout << "从文件打开哈夫曼树\n";
ifstream infile;
infile.open("SavedHfmTree.dat", ios::binary | ios::in);
if (infile.fail()) {
cout << "SavedHfmTree文件打开失败\n";
return;
}
infile.read((char*)&num, sizeof num);
infile.read((char*)&root, sizeof root);
for (int i = 0;i < totalNumber;i++) {
infile.read((char*)&elem[i], sizeof elem[i]);
}
}
char choice;
string context = "";
cout << "从文件(1)或键盘(2)读取内容进行编码(1/2)\n";
cin >> choice;
if (choice == '1') {
ifstream file("Context.txt");
if (file.fail()) {
cout << "Context文件打开失败\n";
return;
}
E ch;
while (file.get(ch)) {
context += ch;
}
file.close();
cout << "需要编码的内容为: " << context << "\n";
} else {
cin.ignore();
cout << "请输入需要编码的内容: \n";
getline(cin, context, '\n');
int siz = context.size();
cout << "需要编码的内容为: " << context << "\n";
}
ofstream outfile("EncodedContext.txt", ios::trunc);
string code = "";
bool f = 1;
for (int i = 0;i < context.size();i++) {
int j = 0;
for (j = 0;j < num;j++) {
if (context[i] == elem[j].data) {
int k = 0;
while (elem[j].code[k] != '\0')
code += elem[j].code[k++];
break;
}
}
if (j == num) {
f = 0;break;
}
}
if (!f) {
cout << "编码失败!\n";
return;
} else {
outfile << code;
outfile.close();
cout << "编码成功!编码结果为: " << code << "\n";
cout << "已保存到EncodedContext文件\n";
}
}
template<class T, class E>
void HFTree<T, E>::Decoding() {
cout << "===========================\n";
cout << "=========Decoding==========\n";
string decode = "";
string code = "";
string word = "";
E ch;
cout << "对EncodedContext.txt文件内容进行解码...\n";
ifstream infile("EncodedContext.txt");
if (infile.fail()) {
cout << "文件打开失败!\n";
return;
}
int siz = 0;
while (infile.get(ch)) {
code += ch;
siz++;
}
cout << "需要解码的内容为: " << code << "\n";
int i = 0, j = 0;
for (i = 0;i < siz;i++) {
word += code[i];
for (j = 0;j < num;j++) {
int k = 0;
string hfcode = "";
while (elem[j].code[k] != '\0')hfcode += elem[j].code[k++];
if (hfcode == word) {
decode += elem[j].data;
word = "";
break;
}
}
}
if (i == siz && j == num) {
cout << "解码失败!\n";
return;
}
ofstream outfile("DecodedContext.txt");
outfile << decode;
outfile.close();
cout << "解码成功!解码结果为: " << decode << "\n";
cout << "已保存到DecodedContext中\n";
}
void test() {
HFTree<double, char>t;
t.Initialization();
t.preintHFTree();
t.Encoding();
t.Decoding();
}
参考书籍推荐:参考书籍
有用的话点个赞吧~关注 @曾续缘,持续获取更多资料。