哈夫曼树又称最优二叉树,是带权路径长度最短的树,可用来构造最优编码,用于信息传输、数据压缩等方面,是一种应用广泛的二叉树。
几个相关的基本概念:
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 <typename T>
struct HuffmanNode
{
//初始化
HuffmanNode(T k,HuffmanNode<T>* l,HuffmanNode<T>* r):key(k),lchild(l),rchild(r),flag(0) {}
T key; //节点权值
HuffmanNode<T>* lchild; //节点左孩
HuffmanNode<T>* rchild; //节点右孩
int flag; //标志 判断是否从森林中删除
};
哈夫曼树的抽象数据类型
template <typename T>
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<T>* pnode); //前序遍历二叉树
void inOrder(HuffmanNode<T>* pnode); //中序遍历二叉树
void postOrder(HuffmanNode<T>* pnode); //后序遍历二叉树
void print(HuffmanNode<T>* pnode); //打印二叉树
void destory(HuffmanNode<T>* pnode); //销毁二叉树
HuffmanNode<T>* root; //哈夫曼树根节点
HuffmanNode<T>* forest[MAXSIZE]; //用数组来存储森林中树的根节点
};
具体实现
/*自写排序*/
template <typename T>
void Huffman<T>::my_sort(int size)
{
for(int i=0;i<size-1;i++)
{
for(int j=i+1;j<size;j++)
{
if(forest[i]->key > forest[j]->key)
{
swap(forest[i],forest[j]);
}
else
continue;
}
}
};
/*创建哈夫曼树*/
template <typename T>
void Huffman<T>::creat(T a[],int size)
{
int j,k=0;
/*每个节点都作为一个森林*/
for(int i=0; i<size; i++)
{
HuffmanNode<T>* ptr = new HuffmanNode<T>(a[i],NULL,NULL);
forest[i] = ptr; //双向队列尾部加入一个元素
}
for(int i=0; i<size-1; i++)
{
/*排序 选出根节点权值最小的两棵树*/
my_sort(size+k);
for(j=0; ; j++)
{
if(forest[j]->flag!=1 && forest[j+1]->flag != 1)
{
/*构建新节点*/
HuffmanNode<T>* node = new HuffmanNode<T>(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 <typename T>
void Huffman<T>::preOrder(HuffmanNode<T>* pnode)
{
if(pnode != NULL)
{
cout << pnode -> key;
preOrder(pnode->lchild);
preOrder(pnode->rchild);
}
};
template <typename T>
void Huffman<T>::preOrder()
{
preOrder(root);
};
/*中序遍历哈夫曼树*/
template <typename T>
void Huffman<T>::inOrder(HuffmanNode<T>* pnode)
{
if(pnode != NULL)
{
inOrder(pnode->lchild);
cout << pnode -> key;
inOrder(pnode->rchild);
}
};
template <typename T>
void Huffman<T>::inOrder()
{
inOrder(root);
};
/*后序遍历哈夫曼树*/
template <typename T>
void Huffman<T>::postOrder(HuffmanNode<T>* pnode)
{
if(pnode != NULL)
{
postOrder(pnode->lchild);
postOrder(pnode->rchild);
cout << pnode -> key;
}
};
template <typename T>
void Huffman<T>::postOrder()
{
postOrder(root);
};
/*打印哈夫曼树*/
template <typename T>
void Huffman<T>::print(HuffmanNode<T>* 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 <typename T>
void Huffman<T>::print()
{
print(root);
};
/*销毁哈夫曼树*/
template <typename T>
void Huffman<T>::destory(HuffmanNode<T>* pnode)
{
if( pnode!= NULL)
{
destory(pnode->lchild);
destory(pnode->rchild);
delete pnode;
pnode = NULL;
}
};
template <typename T>
void Huffman<T>::destory()
{
destory(root);
};
哈夫曼树代码测试
int main()
{
Huffman<int> 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
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); //释放工作空间
} //HuffmanCoding
void 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];
}
}
}
} //HuffmanCode
int 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<n;i++) //录入权值
scanf("%d",&w[i]);
HC=(char **)malloc((n+1)*sizeof(char*)); //0空间未用
HT=(HuffmanTree)malloc((2*n+1+1)*sizeof(HTNode));//0空间未用
printf("仅编码选择1,译码请选择2: ");
scanf("%d",&choice);
if(choice == 1)
{
create_HuffmanTree(HT,HC,w,n);
for (i = 1; i<n+1; i++)
{
printf("%s\n", HC[i]); //输出哈夫曼编码
free(HC[i]); //释放空间
}
}
if(choice == 2)
HuffmanCoding(HT,HC,w,n);
free(HC);
free(HT);
return 0;
}