【数据结构】3-2哈夫曼树的实现(数组实现)以及哈夫曼编码
哈夫曼树的性质:
- 哈夫曼树不唯一(即左孩子右孩子放的顺序可以是左大右小也可以是左小右大)
- 哈夫曼树的子树也是哈夫曼树
- 哈夫曼树中无度为1的结点
- 有n个叶子结点的哈夫曼树,其总结点数为2*n-1(非常重要!编程实现就要用这条性质)
总体思路:
- 对两个最小者的选择为双亲下标为0的结点
- 对选出的两个最小者,修改双亲下标为新结点的下标
- 新结点的左右孩子修改为所选的两个新结点的下标
- 新结点的权值为两个结点的权值之和
结构体定义:
struct element { int weight; // 权值域 int lchild, rchild, parent; // 该结点的左、右、双亲结点在数组中的下标 };
哈夫曼树类的定义:
class HTree { private: element *node; int n, m;//n是元素个数 stack<int> code; int setcode(int weight);//返回的是编码的长度 public: HTree(int _n, int _weight[]); void select(int &s1, int &s2); void creatHT(); void print(); int search(int weight); void printHTcode(); };
构造函数的实现:
HTree::HTree(int _n,int _weight[]) { n = _n; m = 2 * n - 1; //n个元素需要2*n-1空间 node = new element[m]; //开辟m个大小的element空间 for (int i = 0; i <n; i++) { node[i].weight = _weight[i];//为每一个元素赋初始权值 } for (int i = 0; i < m; i++) { node[i].lchild = node[i].rchild = node[i].parent = 0;//初始化左孩子右孩子和双亲指针,都为0 } creatHT();//构造哈夫曼树 }
选择函数,比较结点中双亲为0的较小的两个(在本文中s1最小):
void HTree::select(int &s1, int &s2) { //对两个最小者的选择为双亲下标为0的结点 for (int i = 0; i < n; i++) { if (node[i].parent == 0) { s1=i; //先找到一个临时比较元素进行最小值比较 break; } } for (int i = 0; i <n; i++) //该循环是找到一个最小的并赋值给s1 { if ((node[s1].weight > node[i].weight) && (node[i].parent == 0)) //找双亲为0的 { s1 = i; } } //同s1的比较方法,先找到一个双亲为0的临时值(排除s1),然后再进行逐一比较找到第二小的值赋值给s2即可 for (int i = 0; i <n; i++) { if ((node[i].parent == 0)&&i!=s1) //排除s1 { s2 = i; //先找到一个临时比较元素进行最小值比较 break; } } for (int i = 0; i <n; i++) //找到除s1以外双亲为0中最小的并赋值给s2 { if ((node[s2].weight > node[i].weight) && (node[i].parent == 0) && (i != s1)) { s2 = i; } } }
创建哈夫曼树:
- 循环2*n-1 - n-1次,即进行m-n-1次比较即可
- 对两个最小者的选择为双亲下标为0的结点(调用选择函数来实现)
- 对选出的两个最小者,修改双亲下标为新结点的下标
- 新结点的左右孩子修改为所选的两个新结点的下标
- 新结点的权值为两个结点的权值之和
void HTree::creatHT() { for (int i = n; i < m; i++)//执行的是m-n次比较 { int s1, s2;//左小右大 select(s1, s2);//选择数组中最小且双亲为0的结点 node[s1].parent = i;//找到后的s1,s2的双亲应该放在n-1后 node[s2].parent = i; node[i].lchild = s1; //左小 node[i].rchild = s2; //右大 node[i].weight = node[s1].weight + node[s2].weight; n++; } }
哈夫曼编码:
原理:
- 输入要编码的权值,调用search函数遍历查找到在数组中的地址并返回
- 从要编码的位置上溯遍历,并判断是左孩子还是右孩子,左孩子为0右孩子为1(也可以反过来)
- 编码是从上到下,而我们在遍历的时候是从下到上,因此可以使用栈或者链表的头插法进行存储操作(我在这里使用的是栈,有点不好,输出栈就被清空了,可能会影响时间效率)
- 判断如果该结点的parent为空(即为0)停止循环即可
setcode(私有函数)
int HTree::setcode(int weight) { int location, parent, k;//location来定位权值所指向的位置,parent即Location的双亲,k是移动指针 int cont = 0; location = search(weight); k = location; parent = node[location].parent; while (node[k].parent!= 0&&k!=-1) { if (node[parent].lchild == k) { code.push(0); } else { code.push(1); } k = node[k].parent; parent = node[k].parent; cont++; } if (k == -1) { cout << "未找到!错误!" << endl; } return cont; }
search函数
int HTree::search(int weight) { int result = -1; for (int i = 0; i < m; i++) { if (node[i].weight == weight) { result = i; } } return result; }
完整代码:
/* 1.对两个最小者的选择为双亲下标为0的结点 2.对选出的两个最小者,修改双亲下标为新结点的下标 3.新结点的左右孩子修改为所选的两个新结点的下标 4.新结点的权值为两个结点的权值之和 */ #include<iostream> #include<iomanip> #include<stack> using namespace std; struct element { int weight; // 权值域 int lchild, rchild, parent; // 该结点的左、右、双亲结点在数组中的下标 }; class HTree { private: element *node; int n, m;//n是元素个数 stack<int> code; int setcode(int weight);//返回的是编码的长度哼哼 public: HTree(int _n, int _weight[]); void select(int &s1, int &s2); void creatHT(); void print(); int search(int weight); void printHTcode(); }; int HTree::search(int weight) { int result = -1; for (int i = 0; i < m; i++) { if (node[i].weight == weight) { result = i; } } return result; } int HTree::setcode(int weight) { int location, parent, k;//location来定位权值所指向的位置,parent即Location的双亲,k是移动指针 int cont = 0; location = search(weight); k = location; parent = node[location].parent; while (node[k].parent!= 0&&k!=-1) { if (node[parent].lchild == k) { code.push(0); } else { code.push(1); } k = node[k].parent; parent = node[k].parent; cont++; } if (k == -1) { cout << "未找到!错误!" << endl; } return cont; } void HTree::printHTcode() { cout << "请输入要查询的编码的权值并继续:" << endl; int weight, size; cin >> weight; size=setcode(weight); cout << "权值 " << weight << "编码长度: " << size << endl; cout << weight << " 编码结果为: "; for (int i = 0; i < size; i++) { cout << code.top() << " "; code.pop(); } cout << endl; } HTree::HTree(int _n,int _weight[]) { n = _n; m = 2 * n - 1; //n个元素需要2*n-1空间 node = new element[m]; //开辟m个大小的element空间 for (int i = 0; i <n; i++) { node[i].weight = _weight[i];//为每一个元素赋初始权值 } for (int i = 0; i < m; i++) { node[i].lchild = node[i].rchild = node[i].parent = 0;//初始化左孩子右孩子和双亲指针,都为0 } creatHT();//构造哈夫曼树 } void HTree::select(int &s1, int &s2) { //对两个最小者的选择为双亲下标为0的结点 for (int i = 0; i < n; i++) { if (node[i].parent == 0) { s1=i; //先找到一个临时比较元素进行最小值比较 break; } } for (int i = 0; i <n; i++) //该循环是找到一个最小的并赋值给s1 { if ((node[s1].weight > node[i].weight) && (node[i].parent == 0)) //找双亲为0的 { s1 = i; } } //同s1的比较方法,先找到一个双亲为0的临时值(排除s1),然后再进行逐一比较找到第二小的值赋值给s2即可 for (int i = 0; i <n; i++) { if ((node[i].parent == 0)&&i!=s1) //排除s1 { s2 = i; //先找到一个临时比较元素进行最小值比较 break; } } for (int i = 0; i <n; i++) //找到除s1以外双亲为0中最小的并赋值给s2 { if ((node[s2].weight > node[i].weight) && (node[i].parent == 0) && (i != s1)) { s2 = i; } } } void HTree::creatHT() { for (int i = n; i < m; i++)//执行的是m-n次比较 { int s1, s2;//左小右大 select(s1, s2);//选择数组中最小且双亲为0的结点 node[s1].parent = i;//找到后的s1,s2的双亲应该放在n-1后 node[s2].parent = i; node[i].lchild = s1; //左小 node[i].rchild = s2; //右大 node[i].weight = node[s1].weight + node[s2].weight; n++; } } void HTree::print() { cout << "index weight parent lChild rChild" << endl; cout << left; // 左对齐输出 for (int i = 0; i < m; ++i) { cout << setw(5) << i << " "; cout << setw(6) << node[i].weight << " "; cout << setw(6) << node[i].parent << " "; cout << setw(6) << node[i].lchild << " "; cout << setw(6) << node[i].rchild << endl; } cout << "哈夫曼树打印完毕!" << endl; } int main() { cout << "请输入要构造哈夫曼树元素的个数: "; int n,*weight; cin >> n; weight = new int[n + 1]; cout << "请输入这" << n << "个元素的权值:" << endl; for (int i = 0; i < n; i++) { cin >> weight[i]; } HTree test(n, weight); test.print(); test.printHTcode(); system("pause"); return 0; }
测试数据
4 5 2 4 7 4
结果:
请输入要构造哈夫曼树元素的个数: 4 请输入这4个元素的权值: 5 2 4 7 index weight parent lChild rChild 0 5 5 0 0 1 2 4 0 0 2 4 4 0 0 3 7 6 0 0 4 6 5 1 2 5 11 6 0 4 6 18 0 3 5 哈夫曼树打印完毕! 请输入要查询的编码的权值并继续: 4 权值 4编码长度: 3 4 编码结果为: 1 1 1 请按任意键继续. . .
特别说明:
这个代码绝对不是最优解:)
但是原理是对的,嘛毕竟自己能力有限~
只要原理懂了可以自己优化优化的~~
各位见笑了,还请多多包涵。