“哈夫曼树”介绍
给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)
特点
哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近
相关术语
1.路径和路径长度
在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1
2.结点的权及带权路径长度
若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积
3.树的带权路径长度
树的带权路径长度规定为所有叶子结点的带权路径长度之和
前期准备
结构体——存储结点信息
struct ElemType{
int weight;
int parent,lchild,rchild;
};
类的声明
class HuffmanTree
{
public:
HuffmanTree(int w[ ], int n);
HuffmanTree( );
void Print( );
private:
ElemType *huffTree; //数组用于存储哈夫曼树
int num; //统计叶子结点数量
void Select(int n, int &i1, int &i2); //选取叶子函数
};
类的实现
成员函数
1.构造函数
使用数组存储结点数据。
构造思路:
(1)对结构体数组初始化(包括建立空间、标记父子关系)并将叶子结点 w1、w2、…,wn的权值抄入结构体数组中;
(2) 选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;并对新结点和子树进行匹配;
(3)将新树加入数组中(放入叶子结点后面的位置);
(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。
含n个叶子结点的二叉树经过合并后,分支结点共(n-1)个,哈夫曼树的结点数为(2n-1).
HuffmanTree::HuffmanTree(int w[ ], int n){
//构造哈夫曼树
num=n;
huffTree=new ElemType[2*n-1]; //建立存储数组
int i,k,i1,i2; //i为叶子结点循环变量,k标记求和后的节点,i1、i2标记最小次小元素位置
for(int i=0;i<2*num-1;i++){ //初始化,将parent、lchild、rchild均置为-1
huffTree[i].parent=-1;
huffTree[i].lchild=huffTree[i].rchild=-1;
}
for(int i=0;i<num;i++){
huffTree[i].weight=w[i]; //传叶子结点
}
for(int k=num;k<2*num-1;k++){ //k从数组最后开始,对应权值存储权重数组内最小的俩元素下标
Select( k ,i1,i2 );
huffTree[k].weight = huffTree[i1].weight+huffTree[i2].weight;
huffTree[i1].parent= k; huffTree[i2].parent=k;
huffTree[k].lchild = i1;huffTree[k].rchild = i2;
}
}
2.选取叶子函数Select
遍历叶子结点,对最小和次小的叶子结点合并,为建哈夫曼树做准备
程序声明时,对i1、i2使用引用参数,使得i1、i2的值都可返回到调用位置,并参与调用位置后面的运算。
程序执行前,i1、i2为异常值,函数执行时,从第一个不存在父节点的叶子结点(未进行父子配对的结点)开始,依次找寻最小和次小结点,更新i1、i2的数值。
P.S.程序执行过程,大家可以自行列表模拟,代码不难理解~~
上代码:
void HuffmanTree::Select(int n, int &i1, int &i2)
{
int temp;
int i = 0;
for ( ; i < n; i++)
if (huffTree[i].parent == -1) {i1 = i; break;}
for (i = i + 1; i < n; i++)
if (huffTree[i].parent == -1) {i2 = i;break;}
if (huffTree[i1].weight > huffTree[i2].weight) //左边大于右边
{
temp = i1; i1 = i2;i2 = temp; //小的是左孩子、大的是右孩子
}
//从i2的下一位置开始遍历,继续找小的元素,更新i1、i2的值
for (i = i + 1; i < n; i++)
{
if (huffTree[i].parent == -1)
{
if (huffTree[i].weight < huffTree[i1].weight)
{
i2 = i1; i1 = i;
}
else if (huffTree[i].weight < huffTree[i2].weight)
{
i2 = i;
}
}
}
}
3.析构
使用数组静态存储,无需释放空间,析构函数为空
4.输出函数
输出构造的二叉树中叶子结点到根结点的路径及每个结点的权值
输出时,从叶子结点出发,一直到根结点,输出路径及权重。
void HuffmanTree::Print( ){
//输出构造的二叉树中叶子结点到根结点的路径及每个结点的权值(每个结点的数值为孩子权值之和)
int i,k;//i用来遍历最初的叶子结点,K用来遍历有孩子的结点(非叶子结点)
cout << "叶子到根结点的路径:" << endl;
for(i=0;i<num;i++){
cout << huffTree[i].weight;
k = huffTree[i].parent;
while (k != -1) //循环输出结点
{
cout << "-->" << huffTree[k].weight;
k = huffTree[k].parent; //继续往上找叶子结点的父结点的父结点(直到根结点)
}
cout << endl;
}
}
类的完整代码
class HuffmanTree
{
public:
HuffmanTree(int w[ ], int n);
HuffmanTree( );
void Print( );
private:
ElemType *huffTree;
int num;
void Select(int n, int &i1, int &i2);
};
void HuffmanTree::Select(int n, int &i1, int &i2)
{
int temp;
int i = 0;
for ( ; i < n; i++)
if (huffTree[i].parent == -1) {i1 = i; break;}
for (i = i + 1; i < n; i++)
if (huffTree[i].parent == -1) {i2 = i;break;}
if (huffTree[i1].weight > huffTree[i2].weight) //左边大于右边
{
temp = i1; i1 = i2;i2 = temp; //小的是左孩子、大的是右孩子
}
//从i2的下一位置开始遍历,继续找小的元素,更新i1、i2的值
for (i = i + 1; i < n; i++)
{
if (huffTree[i].parent == -1)
{
if (huffTree[i].weight < huffTree[i1].weight)
{
i2 = i1; i1 = i;
}
else if (huffTree[i].weight < huffTree[i2].weight)
{
i2 = i;
}
}
}
}
HuffmanTree::HuffmanTree(int w[ ], int n){
//构造哈夫曼树
num=n;
huffTree=new ElemType[2*n-1]; //建立存储数组
int i,k,i1,i2; //i为叶子结点循环变量,k标记求和后的节点,i1、i2标记最小次小元素位置
for(int i=0;i<2*num-1;i++){ //初始化,将parent、lchild、rchild均置为-1
huffTree[i].parent=-1;
huffTree[i].lchild=huffTree[i].rchild=-1;
}
for(int i=0;i<num;i++){
huffTree[i].weight=w[i]; //传叶子结点
}
for(int k=num;k<2*num-1;k++){ //k从数组最后开始,对应权值存储权重数组内最小的俩元素下标
Select( k ,i1,i2 );
huffTree[k].weight = huffTree[i1].weight+huffTree[i2].weight;
huffTree[i1].parent= k; huffTree[i2].parent=k;
huffTree[k].lchild = i1;huffTree[k].rchild = i2;
}
}
void HuffmanTree::Print( ){
//输出构造的二叉树中叶子结点到根结点的路径及每个结点的权值(每个结点的数值为孩子权值之和)
int i,k;//i用来遍历最初的叶子结点,K用来遍历有孩子的结点(非叶子结点)
cout << "叶子到根结点的路径:" << endl;
for(i=0;i<num;i++){
cout << huffTree[i].weight;
k = huffTree[i].parent;
while (k != -1) //循环输出结点
{
cout << "-->" << huffTree[k].weight;
k = huffTree[k].parent; //继续往上找叶子结点的父结点的父结点(直到根结点)
}
cout << endl;
}
}
主函数
主函数部分使用C++动态数组,可自定义叶子节点数目。通过调用类的对象,完成建树并输出。
int main(){
int n;
cout<<"输入叶子结点的数量:";
cin>>n;
int *h = new int [n];
cout<<"输入叶子结点的权值:";
for(int i=0;i<n;i++)
cin>>h[i];
cout<<"-------------------------------------"<<endl;
cout<<"叶子结点的权值分别是:";
for(int i=0;i<n;i++)
cout<<h[i]<<" ";
cout<<endl;
cout<<"-------------------------------------"<<endl;
HuffmanTree H(h, 4);
H.Print();
delete []h;
return 0;
}
程序完整源码
#include<iostream>
using namespace std;
struct ElemType{
int weight;
int parent,lchild,rchild;
};
class HuffmanTree
{
public:
HuffmanTree(int w[ ], int n);
HuffmanTree( );
void Print( );
private:
ElemType *huffTree;
int num;
void Select(int n, int &i1, int &i2);
};
void HuffmanTree::Select(int n, int &i1, int &i2)
{
int temp;
int i = 0;
for ( ; i < n; i++)
if (huffTree[i].parent == -1) {i1 = i; break;}
for (i = i + 1; i < n; i++)
if (huffTree[i].parent == -1) {i2 = i;break;}
if (huffTree[i1].weight > huffTree[i2].weight) //左边大于右边
{
temp = i1; i1 = i2;i2 = temp; //小的是左孩子、大的是右孩子
}
//从i2的下一位置开始遍历,继续找小的元素,更新i1、i2的值
for (i = i + 1; i < n; i++)
{
if (huffTree[i].parent == -1)
{
if (huffTree[i].weight < huffTree[i1].weight)
{
i2 = i1; i1 = i;
}
else if (huffTree[i].weight < huffTree[i2].weight)
{
i2 = i;
}
}
}
}
HuffmanTree::HuffmanTree(int w[ ], int n){
//构造哈夫曼树
num=n;
huffTree=new ElemType[2*n-1]; //建立存储数组
int i,k,i1,i2; //i为叶子结点循环变量,k标记求和后的节点,i1、i2标记最小次小元素位置
for(int i=0;i<2*num-1;i++){ //初始化,将parent、lchild、rchild均置为-1
huffTree[i].parent=-1;
huffTree[i].lchild=huffTree[i].rchild=-1;
}
for(int i=0;i<num;i++){
huffTree[i].weight=w[i]; //传叶子结点
}
for(int k=num;k<2*num-1;k++){ //k从数组最后开始,对应权值存储权重数组内最小的俩元素下标
Select( k ,i1,i2 );
huffTree[k].weight = huffTree[i1].weight+huffTree[i2].weight;
huffTree[i1].parent= k; huffTree[i2].parent=k;
huffTree[k].lchild = i1;huffTree[k].rchild = i2;
}
}
void HuffmanTree::Print( ){
//输出构造的二叉树中叶子结点到根结点的路径及每个结点的权值(每个结点的数值为孩子权值之和)
int i,k;//i用来遍历最初的叶子结点,K用来遍历有孩子的结点(非叶子结点)
cout << "叶子到根结点的路径:" << endl;
for(i=0;i<num;i++){
cout << huffTree[i].weight;
k = huffTree[i].parent;
while (k != -1) //循环输出结点
{
cout << "-->" << huffTree[k].weight;
k = huffTree[k].parent; //继续往上找叶子结点的父结点的父结点(直到根结点)
}
cout << endl;
}
}
int main(){
int n;
cout<<"输入叶子结点的数量:";
cin>>n;
int *h = new int [n];
cout<<"输入叶子结点的权值:";
for(int i=0;i<n;i++)
cin>>h[i];
cout<<"-------------------------------------"<<endl;
cout<<"叶子结点的权值分别是:";
for(int i=0;i<n;i++)
cout<<h[i]<<" ";
cout<<endl;
cout<<"-------------------------------------"<<endl;
HuffmanTree H(h, 4);
H.Print();
delete []h;
return 0;
}