-
哈夫曼树
-
哈夫曼树概述
(1)总括:哈夫曼(Huffman)树又称为最优二叉树,是一类带权路径长度最短的树
(2)树的路径长度:树根到每个结点的路径长度之和(对于节点个数相同的二叉树来说,路径长度最短树是完全二叉树)
(3)权值(Weight):每个叶子结点所赋予的特殊值
(4)叶子带权路径长度:一个叶子结点的权值与该结点到根的路径长度的乘积
(5)二叉树的带权路径长度(Weighted Path Length):简记WPL,所有叶子带权路径长度之和
WPL=
(w是第k个叶子结点的权值,l为第k个叶子结点的路径长度)给定n个叶子结点,在这些结点构造的二叉树中,WPL最小的二叉树称为哈夫曼树
-
哈夫曼树的构造
(1)思路:
必须使权值相对大的叶子结点更靠近根结点。
(2)哈夫曼算法:
左右孩子都为空(说白了就是哈夫曼树的原始叶子节点)
选取两棵根节点最小的树作为左右子树构造一棵新的二叉树,并且该二叉树的根节点权值为左右子树根结点权值之和
删除这两棵被结合的二叉树,同时新的二叉树加入F
重复b c,直到F中只剩下一棵二叉树,这棵二叉树就是哈夫曼树
(3)总结下规律
成为哈夫曼树的叶子结点,所以:原始二叉树数n=哈夫曼树叶子结点数
共结合n-1次,每次结合新形成一个结点
具有n个叶子结点的哈夫曼树具有n+n-1=2n-1个结点
(4)哈夫曼优化算法
小于右孩子树根结点权重
#define Length 10 struct Huffman_Node //哈夫曼树节点 { int Index; //刚输入权重的下标 int Weight; //权重 Huffman_Node* Parent; //父母节点 Huffman_Node* Left_Child; //左孩子 Huffman_Node* Right_Child; //右孩子 }; bool MyCompare(Huffman_Node* A, Huffman_Node* B) //自定义排序,将Parent=NULL(未结合的节点)与已经结合的节点分开,未结合的在数组下部分 { //两部分都按照权重升序排序 if (A->Parent == NULL && !B->Parent == NULL) return A->Weight < B->Weight; else if (A->Parent == NULL && B->Parent != NULL) return false; else if (A->Parent != NULL && B->Parent == NULL) return true; else return A->Weight < B->Weight; } bool _MyCompare(Huffman_Node* A, Huffman_Node* B) //自定义排序,按节点一开始的输入顺序排序 { return A->Index < B->Index; } //Initial_NodeNum 是初始节点个数,也就是所构建的哈夫曼树的树叶数 //*Weight_List 权重值 Huffman_Tree::Huffman_Tree(int Initial_NodeNum, int *Weight_List):Leaf_Node_Num(Initial_NodeNum) { Huffman_List = new Huffman_Node*[2 * Initial_NodeNum - 1]; //分配哈夫曼树节点数组,个数为 2*Leaf_Num-1 for (int i = 0; i < 2 * Initial_NodeNum - 1; i++) //节点数组下标从0开始 { Huffman_List[i] = new Huffman_Node; //分配节点 if (i < Initial_NodeNum) //初始化原始叶子节点 { Huffman_List[i]->Index = i; //记录叶子权重输入顺序(下标) Huffman_List[i]->Weight = Weight_List[i]; } //孩子和父母节点全部初始为空(NULL) Huffman_List[i]->Left_Child = NULL; Huffman_List[i]->Right_Child = NULL; Huffman_List[i]->Parent = NULL; } int Begin = 0; //Begin是未结合节点(包括新结合成的节点)在数组中开始的位置---因为数组的前一部分是已经组合的节点 int Temp_Num = Initial_NodeNum; while (Initial_NodeNum != 2 * Temp_Num - 1) //当树节点达到2*Leaf_Num-1时说明哈夫曼树构建完成 { sort(&Huffman_List[Begin], Huffman_List + Initial_NodeNum, MyCompare); //进行排序,下标为Begin,Begin+1的节点为当前权重最小的两个节点 Huffman_List[Initial_NodeNum]->Index = Initial_NodeNum; //保存下标 Huffman_List[Initial_NodeNum]->Weight = Huffman_List[Begin]->Weight + Huffman_List[Begin + 1]->Weight; //新结合节点的权重为两个节点权重的和 Huffman_List[Initial_NodeNum]->Left_Child = Huffman_List[Begin]; //为了统一,值小的为左孩子 Huffman_List[Initial_NodeNum]->Right_Child = Huffman_List[Begin + 1]; //被结合的两个节点的父母亲为新结合的节点 Huffman_List[Begin]->Parent = Huffman_List[Initial_NodeNum]; Huffman_List[Begin + 1]->Parent = Huffman_List[Initial_NodeNum]; Begin += 2; //两个节点没了,数组下标后移 Initial_NodeNum += 1; //节点数加1 } //哈夫曼树构建完成 //为了保持与输入一致,按输入权重及新节点产生顺序进行排序 sort(Huffman_List, Huffman_List + Initial_NodeNum, _MyCompare); Root_Node = Huffman_List[Initial_NodeNum-1]; //保存树根,树根为最后生成的节点 }
-
哈夫曼编码
(1)规则:在哈夫曼树中,规定所有结点的左分支代表0,右分支代表1,从根结点到某一叶子结点所经过的路径组成的0 1序列便为该叶子节点字符的哈夫曼编码。(哈夫曼编码针对的是哈夫曼树的叶子节点)
(2)编码实现思路:在编程中一个叶子结点对于内容编码的确定,是需要以叶子为起点爬到根节点进行确定,而怎么知道当前结点是左是右?我们知道当前结点保存了其父母结点的地址,所以只需要当前结点是其父母结点的左孩子还是右孩子就可以确定该段的编码,然后一直找到根结点编码完成。
实现啦实现啦
struct Huffman_Code //储存编码的结构 { char Code[Length]; //储存编码 int Depth; //编码长度+1(对应为该叶子节点的度+1) }; void Huffman_Tree::_ExcuteCode() //进行编码 { for (int i = 0; i < Leaf_Node_Num; i++) { Huffman_Node* _Parent = Huffman_List[i]->Parent; //_Parent 当前节点的父母节点 Huffman_Node* _Child = Huffman_List[i]; //_child 当前节点 Huffman_Code_List[i].Depth = -1; while (_Parent != NULL) { if (_Child == _Parent->Left_Child) Huffman_Code_List[i].Code[++Huffman_Code_List[i].Depth] = '0'; //是左孩子,该段编码0 else Huffman_Code_List[i].Code[++Huffman_Code_List[i].Depth] = '1'; //右孩子,编码1 //往树根走 _Child = _Parent; _Parent = _Parent->Parent; } } }
-
哈夫曼树类实现(全部代码)
#include<iostream> #include<algorithm> using namespace std; #define Length 10 struct Huffman_Node //哈夫曼树节点 { int Index; //刚输入权重的下标 int Weight; //权重 Huffman_Node* Parent; //父母节点 Huffman_Node* Left_Child; //左孩子 Huffman_Node* Right_Child; //右孩子 }; struct Huffman_Code //储存编码的结构 { char Code[Length]; //储存编码 int Depth; //编码长度+1(对应为该叶子节点的度+1) }; bool MyCompare(Huffman_Node* A, Huffman_Node* B) //自定义排序,将Parent=NULL(未结合的节点)与已经结合的节点分开,未结合的在数组下部分 { //两部分都按照权重升序排序 if (A->Parent == NULL && !B->Parent == NULL) return A->Weight < B->Weight; else if (A->Parent == NULL && B->Parent != NULL) return false; else if (A->Parent != NULL && B->Parent == NULL) return true; else return A->Weight < B->Weight; } bool _MyCompare(Huffman_Node* A, Huffman_Node* B) //自定义排序,按节点一开始的输入顺序排序 { return A->Index < B->Index; } class Huffman_Tree { private: Huffman_Node **Huffman_List; //地址数组,数组的每一个元素指向一个节点 Huffman_Code *Huffman_Code_List; //叶子节点编码组 Huffman_Node *Root_Node; //哈夫曼树根节点 int Leaf_Node_Num; //叶子数量 void PreOrder_Op(Huffman_Node* &Root); //先序遍历哈夫曼树的内部实现函数 void _ExcuteCode(); //对叶子节点进行编码的内部实现函数 public: Huffman_Tree(int Initial_NodeNum, int *Weight_List); //构造哈夫曼树 void PreOrder(); //先序遍历接口函数 void Create_Code(); //叶子节点编码外部接口 void Print_Huffman_Code(); //输出所有叶子编码 }; //Initial_NodeNum 是初始节点个数,也就是所构建的哈夫曼树的树叶数 //*Weight_List 权重值 Huffman_Tree::Huffman_Tree(int Initial_NodeNum, int *Weight_List):Leaf_Node_Num(Initial_NodeNum) { Huffman_Code_List = new Huffman_Code[Initial_NodeNum]; //分配编码节点组,多少叶子就有多少编码 Huffman_List = new Huffman_Node*[2 * Initial_NodeNum - 1]; //分配哈夫曼树节点数组,个数为 2*Leaf_Num-1 for (int i = 0; i < 2 * Initial_NodeNum - 1; i++) //节点数组下标从0开始 { Huffman_List[i] = new Huffman_Node; //分配节点 if (i < Initial_NodeNum) //初始化原始叶子节点 { Huffman_List[i]->Index = i; //记录叶子权重输入顺序(下标) Huffman_List[i]->Weight = Weight_List[i]; } //孩子和父母节点全部初始为空(NULL) Huffman_List[i]->Left_Child = NULL; Huffman_List[i]->Right_Child = NULL; Huffman_List[i]->Parent = NULL; } int Begin = 0; //Begin是未结合节点(包括新结合成的节点)在数组中开始的位置---因为数组的前一部分是已经组合的节点 int Temp_Num = Initial_NodeNum; while (Initial_NodeNum != 2 * Temp_Num - 1) //当树节点达到2*Leaf_Num-1时说明哈夫曼树构建完成 { sort(&Huffman_List[Begin], Huffman_List + Initial_NodeNum, MyCompare); //进行排序,下标为Begin,Begin+1的节点为当前权重最小的两个节点 Huffman_List[Initial_NodeNum]->Index = Initial_NodeNum; //保存下标 Huffman_List[Initial_NodeNum]->Weight = Huffman_List[Begin]->Weight + Huffman_List[Begin + 1]->Weight; //新结合节点的权重为两个节点权重的和 Huffman_List[Initial_NodeNum]->Left_Child = Huffman_List[Begin]; //为了统一,值小的为左孩子 Huffman_List[Initial_NodeNum]->Right_Child = Huffman_List[Begin + 1]; //被结合的两个节点的父母亲为新结合的节点 Huffman_List[Begin]->Parent = Huffman_List[Initial_NodeNum]; Huffman_List[Begin + 1]->Parent = Huffman_List[Initial_NodeNum]; Begin += 2; //两个节点没了,数组下标后移 Initial_NodeNum += 1; //节点数加1 } //哈夫曼树构建完成 //为了保持与输入一致,按输入权重及新节点产生顺序进行排序 sort(Huffman_List, Huffman_List + Initial_NodeNum, _MyCompare); Root_Node = Huffman_List[Initial_NodeNum-1]; //保存树根,树根为最后生成的节点 } void Huffman_Tree::PreOrder_Op(Huffman_Node* &Root) //先序,不多说 { if (Root == NULL) return; cout << "Weight: " << Root->Weight << endl; if(Root->Left_Child) PreOrder_Op(Root->Left_Child); if(Root->Right_Child) PreOrder_Op(Root->Right_Child); } void Huffman_Tree::_ExcuteCode() //进行编码 { for (int i = 0; i < Leaf_Node_Num; i++) { Huffman_Node* _Parent = Huffman_List[i]->Parent; //_Parent 当前节点的父母节点 Huffman_Node* _Child = Huffman_List[i]; //_child 当前节点 Huffman_Code_List[i].Depth = -1; while (_Parent != NULL) { if (_Child == _Parent->Left_Child) Huffman_Code_List[i].Code[++Huffman_Code_List[i].Depth] = '0'; //是左孩子,该段编码0 else Huffman_Code_List[i].Code[++Huffman_Code_List[i].Depth] = '1'; //右孩子,编码1 //往树根走 _Child = _Parent; _Parent = _Parent->Parent; } } } void Huffman_Tree::PreOrder() { PreOrder_Op(Root_Node); } void Huffman_Tree::Create_Code() { _ExcuteCode(); } void Huffman_Tree::Print_Huffman_Code() { for (int i = 0; i < Leaf_Node_Num; i++) { int Len = Huffman_Code_List[i].Depth; cout << "Weight :" << Huffman_List[i]->Weight << " Code :"; for (int j = Len; j >= 0; j--) //注意要逆序输出就好 cout << Huffman_Code_List[i].Code[j]; cout << endl; } } //test int main() { int *Weight; int Root_Num; cin >> Root_Num; Weight = new int[Root_Num]; for (int i = 0; i < Root_Num; i++) cin >> Weight[i]; Huffman_Tree Test_Huffman(Root_Num, Weight); Test_Huffman.PreOrder(); Test_Huffman.Create_Code(); Test_Huffman.Print_Huffman_Code(); delete Weight; return 0; }
文章来源: 数据结构-哈夫曼树(Huffman)