博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
哈夫曼树与哈夫曼编码详解及C++模板实现
阅读量:4184 次
发布时间:2019-05-26

本文共 9044 字,大约阅读时间需要 30 分钟。

  哈夫曼树又称最优二叉树,是带权路径长度最短的树,可用来构造最优编码,用于信息传输、数据压缩等方面,是一种应用广泛的二叉树。

这里写图片描述

几个相关的基本概念:

1.路径:从树中一个结点到另一个结点之间的分支序列构成两个节点间的路径

2.路径长度:路径上的分支的条数称为路径长度
3.树的路径长度:从树根到每个结点的路径长度之和称为树的路径长度
4.结点的权:给树中结点赋予一个数值,该数值称为结点的权
5.带权路径长度:结点到树根间的路径长度与结点的权的乘积,称为该结点的带权路径长度
6.树的带权路径长度:树中所有叶子结点的带权路径长度之和,通常记为WPL
7.最优二叉树:在叶子个数n以及各叶子的权值确定的条件下,树的带权路径长度WPL值最下的二叉树称为最优二叉树。

哈夫曼树的建立

由哈夫曼最早给出的建立最优二叉树的带有一般规律的算法,俗称哈夫曼算法。描述如下:

1):初始化:根据给定的n个权值(W1,W2,…,Wn),构造n棵二叉树的森林集合F={T1,T2,…,Tn},其中每棵二叉树Ti只有一个权值为Wi的根节点,左右子树均为空。

2):找最小树并构造新树:在森林集合F中选取两棵根的权值最小的树做为左右子树构造一棵新的二叉树,新的二叉树的根结点为新增加的结点,其权值为左右子树的权值之和。

3):删除与插入:在森林集合F中删除已选取的两棵根的权值最小的树,同时将新构造的二叉树加入到森林集合F中。

4):重复2)和3)步骤,直至森林集合F中只含一棵树为止,这颗树便是哈夫曼树,即最优二叉树。由于2)和3)步骤每重复一次,删除掉两棵树,增加一棵树,所以2)和3)步骤重复n-1次即可获得哈夫曼树。

下图展示了有4个叶子且权值分别为{9,6,3,1}的一棵最优二叉树的建立过程。


这里写图片描述

这里写图片描述

这里写图片描述

C++类模板构造哈夫曼树

哈夫曼树的节点结构
/*哈夫曼树的节点定义*/template 
struct HuffmanNode{ //初始化 HuffmanNode(T k,HuffmanNode
* l,HuffmanNode
* r):key(k),lchild(l),rchild(r),flag(0) {} T key; //节点权值 HuffmanNode
* lchild; //节点左孩 HuffmanNode
* rchild; //节点右孩 int flag; //标志 判断是否从森林中删除};
哈夫曼树的抽象数据类型
template 
class Huffman{public: void preOrder(); //前序遍历哈夫曼树 void inOrder(); //中序遍历哈夫曼树 void postOrder(); //后序遍历哈夫曼树 void creat(T a[],int size); //创建哈夫曼树 void destory(); //销毁哈夫曼树 void print(); //打印哈夫曼树 void my_sort(int size); Huffman():root(NULL) {} ~Huffman(){ destory(root); }private: void preOrder(HuffmanNode
* pnode); //前序遍历二叉树 void inOrder(HuffmanNode
* pnode); //中序遍历二叉树 void postOrder(HuffmanNode
* pnode); //后序遍历二叉树 void print(HuffmanNode
* pnode); //打印二叉树 void destory(HuffmanNode
* pnode); //销毁二叉树 HuffmanNode
* root;  //哈夫曼树根节点 HuffmanNode
* forest[MAXSIZE]; //用数组来存储森林中树的根节点};
具体实现
/*自写排序*/template 
void Huffman
::my_sort(int size){ for(int i=0;i
key > forest[j]->key) { swap(forest[i],forest[j]); } else continue; } }};/*创建哈夫曼树*/template
void Huffman
::creat(T a[],int size){ int j,k=0; /*每个节点都作为一个森林*/ for(int i=0; i
* ptr = new HuffmanNode
(a[i],NULL,NULL); forest[i] = ptr; //双向队列尾部加入一个元素 } for(int i=0; i
flag!=1 && forest[j+1]->flag != 1) { /*构建新节点*/ HuffmanNode
* node = new HuffmanNode
(forest[j]->key + forest[j+1]->key,forest[j],forest[j+1]); /*新节点加入森林中*/ forest[size+k] = node; k++; /*删除两棵权值最小的树*/ forest[j]->flag = 1; forest[j+1]->flag = 1; break; } else continue; } } root = forest[size+k-1];};/*前序遍历哈夫曼树*/template
void Huffman
::preOrder(HuffmanNode
* pnode){ if(pnode != NULL) { cout << pnode -> key; preOrder(pnode->lchild); preOrder(pnode->rchild); }};template
void Huffman
::preOrder(){ preOrder(root);};/*中序遍历哈夫曼树*/template
void Huffman
::inOrder(HuffmanNode
* pnode){ if(pnode != NULL) { inOrder(pnode->lchild); cout << pnode -> key; inOrder(pnode->rchild); }};template
void Huffman
::inOrder(){ inOrder(root);};/*后序遍历哈夫曼树*/template
void Huffman
::postOrder(HuffmanNode
* pnode){ if(pnode != NULL) { postOrder(pnode->lchild); postOrder(pnode->rchild); cout << pnode -> key; }};template
void Huffman
::postOrder(){ postOrder(root);};/*打印哈夫曼树*/template
void Huffman
::print(HuffmanNode
* pnode){ if(pnode != NULL) { cout << "当前结点:" << pnode -> key << "."; if(pnode -> lchild != NULL) cout << "它的左孩子结点为:" << pnode->lchild->key << "."; else cout << "它没有左孩子."; if(pnode -> rchild != NULL) cout << "它的右孩子结点为:" << pnode->rchild->key << "."; else cout << "它没有右孩子."; cout << endl; print(pnode->lchild); print(pnode->rchild); }};template
void Huffman
::print(){ print(root); };/*销毁哈夫曼树*/template
void Huffman
::destory(HuffmanNode
* pnode){ if( pnode!= NULL) { destory(pnode->lchild); destory(pnode->rchild); delete pnode; pnode = NULL; }};template
void Huffman
::destory(){ destory(root);};

哈夫曼树代码测试

int main(){    Huffman
huff; int a[] = {
10,20,30,40}; huff.creat(a,4); huff.print(); return 0;}

输出结果:

当前结点:100.它的左孩子结点为:40.它的右孩子结点为:60.当前结点:40.它没有左孩子.它没有右孩子.当前结点:60.它的左孩子结点为:30.它的右孩子结点为:30.当前结点:30.它没有左孩子.它没有右孩子.当前结点:30.它的左孩子结点为:10.它的右孩子结点为:20.当前结点:10.它没有左孩子.它没有右孩子.当前结点:20.它没有左孩子.它没有右孩子.

  昨天下午就开始写了,本来使用deque双向队列来存储森林中树的根节点,结果在排序找出权值最小的两棵树的时候遇到了麻烦,换了几种方式都是编译错误,折磨了几个小时。后来选择用数组来存储,今天下午试着写了一下,总算整出来了,还有待优化,分享一下吧,整个思路过程都有注释,下来在慢慢改。下面看哈夫曼编码。

#include 
#include
#include
typedef struct{ unsigned int weight; unsigned int parent,lchild,rchild;}HTNode,*HuffmanTree; typedef char **HuffmanCode;int Select(HuffmanTree HT, int n, int *s1, int *s2){ //在HT[1..n]中选择parent为0且weight最小的两个结点,// 其序号分别为s1和s2。 int i,j,k,flag; int count = 0; for(i=1; i<=n; i++) { if(HT[i].parent == 0) count++; } if(count == 0) return 1; *s1 = 0; *s2 = 0; for(i=1; i<=n; i++) { if(HT[i].parent != 0) continue; if(HT[i].weight < HT[*s1].weight) *s1 = i; } for(int j=1; j<=n; j++) { if(HT[j].parent != 0) continue; if(HT[j].weight < HT[*s2].weight && j != *s1) *s2 = j; }}void create_HuffmanTree(HuffmanTree HT,HuffmanCode HC,int *w,int n){ int start; char *cd; // 并求出n个字符的哈夫曼编码HC int i, m, s1=0, s2=0; unsigned int c, f; if (n<=1) return; m = 2 * n - 1; HT = (HuffmanTree)malloc((m+1) * sizeof(HTNode)); // 0号单元未用 HT[0].weight = 99999; for(i=1; i<=n; i++) { //初始化 HT[i].weight=w[i-1]; HT[i].parent=0; HT[i].lchild=0; HT[i].rchild=0; } for (i=n+1; i<=m; i++) { //初始化 HT[i].weight=0; HT[i].parent=0; HT[i].lchild=0; HT[i].rchild=0; } for (i=n+1; i<=m; i++) { // 建哈夫曼树 // 在HT[1..i-1]中选择parent为0且weight最小的两个结点, // 其序号分别为s1和s2。 Select(HT, i-1, &s1, &s2); printf("%d %d\n",s1,s2); HT[s1].parent = i; HT[s2].parent = i; HT[i].lchild = s1; HT[i].rchild = s2; HT[i].weight = HT[s1].weight + HT[s2].weight; } //--- 从叶子到根逆向求每个字符的哈夫曼编码 --- cd = (char *)malloc(n*sizeof(char)); // 分配求编码的工作空间 cd[n-1] = '\0'; // 编码结束符。 for (i=1; i<=n; ++i) { // 逐个字符求哈夫曼编码 start = n-1; // 编码结束符位置 for (c=i, f=HT[i].parent; f!=0; c=f, f=HT[f].parent) // 从叶子到根逆向求编码 if (HT[f].lchild==c) cd[--start] = '0'; else cd[--start] = '1'; HC[i] = (char *)malloc((n-start)*sizeof(char)); // 为第i个字符编码分配空间 strcpy(HC[i], &cd[start]); // 从cd复制编码(串)到HC } free(cd); //释放工作空间} //HuffmanCodingvoid HuffmanCoding(HuffmanTree HT,HuffmanCode HC,int *w,int n){ int start; char *cd; // 并求出n个字符的哈夫曼编码HC int i, m, s1=0, s2=0; unsigned int c, f; if (n<=1) return; m = 2 * n - 1; HT = (HuffmanTree)malloc((m+1) * sizeof(HTNode)); // 0号单元未用 HT[0].weight = 99999; for(i=1; i<=n; i++) { //初始化 HT[i].weight=w[i-1]; HT[i].parent=0; HT[i].lchild=0; HT[i].rchild=0; } for (i=n+1; i<=m; i++) { //初始化 HT[i].weight=0; HT[i].parent=0; HT[i].lchild=0; HT[i].rchild=0; } for (i=n+1; i<=m; i++) { // 建哈夫曼树 // 在HT[1..i-1]中选择parent为0且weight最小的两个结点, // 其序号分别为s1和s2。 Select(HT, i-1, &s1, &s2); HT[s1].parent = i; HT[s2].parent = i; HT[i].lchild = s1; HT[i].rchild = s2; HT[i].weight = HT[s1].weight + HT[s2].weight; } //--- 从叶子到根逆向求每个字符的哈夫曼编码 --- cd = (char *)malloc(n*sizeof(char)); // 分配求编码的工作空间 cd[n-1] = '\0'; // 编码结束符。 for (i=1; i<=n; ++i) { // 逐个字符求哈夫曼编码 start = n-1; // 编码结束符位置 for (c=i, f=HT[i].parent; f!=0; c=f, f=HT[f].parent) // 从叶子到根逆向求编码 if (HT[f].lchild==c) cd[--start] = '0'; else cd[--start] = '1'; HC[i] = (char *)malloc((n-start)*sizeof(char)); // 为第i个字符编码分配空间 strcpy(HC[i], &cd[start]); // 从cd复制编码(串)到HC } free(cd); //释放工作空间 char *code; //译码 code = (char *)malloc(50*sizeof(char)); printf("请输入编码: "); scanf("%s",code); HTNode pnode = HT[m]; for(int q=0; code[q]!='\0'; q++) { if(code[q] == '0') { pnode = HT[pnode.lchild]; if(pnode.rchild == 0 && pnode.lchild == 0) { printf("%d ",pnode.weight); pnode = HT[m]; } } if(code[q] == '1') { pnode = HT[pnode.rchild]; if(pnode.rchild == 0 && pnode.lchild == 0) { printf("%d ",pnode.weight); pnode = HT[m]; } } }} //HuffmanCodeint main(){ int i,n,choice; int *w; HuffmanTree HT; HuffmanCode HC; printf("Node Number: "); scanf("%d",&n); //权值个数 w=(int *)malloc(n*sizeof(int)); printf("Input weights:"); for ( i=0;i
你可能感兴趣的文章
Find the number of subsets such that the sum of numbers in the subset is a prime number
查看>>
CareerCup Binary Tree the Maximum of 人
查看>>
CareerCup Divide n cakes to k different people
查看>>
CareerCup Randomly return a node in a binary tree
查看>>
CareerCup Given a sorted array which contains scores. Write a program to find occurrence
查看>>
CareerCup The number of pairs (x, y) of distinct elements with condition x + y <= Threshold
查看>>
Build a key-value data structure which can perform lookup and rangeLookup(key1, key2)
查看>>
整数划分问题---动态规划、递归
查看>>
Balanced Partition
查看>>
Number of 1s
查看>>
CareerCup Find all the conflicting appointments from a given list of n appointments.
查看>>
CareerCup Given an array having positive integers, find a subarray which adds to a given number
查看>>
CareerCup Generate all the possible substrings
查看>>
CareerCup Given an array A[], find (i, j) such that A[i] < A[j] and (j - i) is maximum.
查看>>
Brain Teaser 球变色
查看>>
(2)考试大纲---信息系统项目管理师考试系列
查看>>
(3)教材目录---信息系统项目管理师考试系列
查看>>
商城基础E-R模型图
查看>>
飞翔的小鸟--键盘事件案例
查看>>
一个sql函数group_concat详解
查看>>