1.赫夫曼树也叫最优二叉树,n个权值构造一颗有n个叶子结点的二叉树,且使叶子结点带权路径长度之和最小,则得到一颗赫夫曼树。
2.赫夫曼树的构造
⑴给定n个权值,构成一个森林的集合F,F中初始为n颗只有一个根节点的二叉树,即n个权值。
⑵每次从F中取出根节点权值最小的二叉树,作为左右子树,得到一颗新树(这里规范一下,左子树根节点权值小于右子树根节点权值,这样可以得到唯一的赫夫曼树)
⑶将新树加入森林F,重复⑵⑶直到森林中仅剩一棵树,这棵树就是构造好的赫夫曼树。
3.赫夫曼编码
⑴前缀编码:若要设计长短不等的编码,则必须满足任一个字符的编码都不能是另一个字符编码的前缀,这种编码成为前缀编码。
⑵利用二叉树可以设置二进制前缀编码,从根节点出发到达目的节点的,设左子树为0,右子树为1,这样就得到了目标节点的二进制前缀编码。
⑶若现在要给一个字符串的所有字符编码,使得最后编码过的字符串总长最短,可以用赫夫曼编码。记录字符串中每个字符串的出现次数,作为赫夫曼树的初始权值,构造出赫夫曼树,按照左0右1的原则即可得到赫夫曼编码。
4.代码
⑴数据结构定义
#include "BiTree.h"
struct TNode
{
int data;
int num; //结点编号
int parent; //父结点、左右孩子结点的编号,从1开始
int lchild;
int rchild;
};
class HTree
{
private:
int n; //初始森林中,树的个数
int n_pos; //森林中树的当前个数
int available_node; //可用结点的编号
TNode *F;
public:
HTree(int a[],int n);
void create_HTree(); //构建赫夫曼树
void print_HTree();
int getAva(); //获取当前available_node
int getData(int num);
Node* to_BiTree(int num); //将顺序存储的赫夫曼树转为 二叉链表形式
int cal_huffman_code(int num,int*a); //计算某结点的赫夫曼编码
};
HTree::HTree(int a[],int nn)
{
n = nn;
n_pos = nn;
available_node = n + 1;
F = new TNode[2*n-1]; //注意,n个点构成的赫夫曼树最多只有2n-1个结点,因为n个点全是叶子结点n0,而n0=n2+1,没有度为1结点,故总数为n0+n2=n+n-1=2*n-1
for (int i = 0; i < n; i++)
{
F[i].data = a[i];
F[i].num = i + 1;
F[i].lchild = 0;
F[i].rchild = 0;
F[i].parent = 0;
}
}
int HTree::getAva()
{
return available_node;
}
int HTree::getData(int num)
{
return F[num - 1].data;
}
⑵构建赫夫曼树
void HTree::create_HTree()
{
while (n_pos > 1)
{
int min_w = INT_MAX;
int num1 = 0, num2 = 0;
for (int i = 0; i < 2*n - 1; i++)
{
if (F[i].data && !F[i].parent&&F[i].data < min_w) //森林中的树
{
min_w = F[i].data;
num1 = F[i].num;
}
}
F[num1 - 1].parent = INT_MAX;
min_w = INT_MAX;
for (int i = 0; i < 2 * n - 1; i++)
{
if (F[i].data && !F[i].parent&&F[i].data < min_w) //森林中的树
{
min_w = F[i].data;
num2 = F[i].num;
}
}
//现在已经找到森林中最小的两个根节点
n_pos -= 2;
F[available_node - 1].data = F[num1 - 1].data + F[num2 - 1].data; //更新父节点的编号、数据、父节点编号
F[available_node - 1].num = available_node;
F[available_node - 1].parent = 0; //注意,这里如果不赋为0,上面森林中查找时会漏掉
F[num1 - 1].parent = available_node;
F[num2 - 1].parent = available_node; //更新孩子结点的父节点编号
if (F[num1 - 1].data < F[num2 - 1].data) //更新父节点的左右孩子编号。 注意:限制左子树根节点权重小于右子树,可得到唯一赫夫曼树
{
F[available_node - 1].lchild = num1;
F[available_node - 1].rchild = num2;
}
else
{
F[available_node - 1].lchild = num2;
F[available_node - 1].rchild = num1;
}
available_node++; //可用结点编号后移
n_pos++; //新树加入森林
}
}
⑶计算某个结点的赫夫曼编码
int HTree::cal_huffman_code(int num,int*a)
{
int i = 0;
while (F[num - 1].parent)
{
if (F[F[num - 1].parent - 1].lchild == num)
{
*(a + i) = 0;
}
else
{
*(a + i) = 1;
}
num = F[num - 1].parent;
i++;
}
return i;
}
⑷赫夫曼树转换为二叉链表结构存储的二叉树
二叉链表详见:https://blog.csdn.net/weixin_42416780/article/details/90145953
Node* HTree::to_BiTree(int num)
{
Node *root = new Node;
root->data = F[num - 1].data;
if (F[num - 1].lchild)
root->lchild = to_BiTree(F[num - 1].lchild);
else
root->lchild = NULL; //这里如果没有左孩子或者右孩子,一定要把相应的指针设为NULL
if (F[num - 1].rchild)
root->rchild = to_BiTree(F[num - 1].rchild);
else
root->rchild = NULL;
return root;
}
⑸打印赫夫曼树
void HTree::print_HTree()
{
cout << “num” << " | " << “data” << " | " << “parent” << " | " << “lchild” << " | " << “rchild”<<endl;
for (int i = 0; i < 2 * n - 1; i++)
{
cout << setfill(’ ‘) << setw(3) << F[i].num << " ";
cout << setfill(’ ‘) << setw(4) << F[i].data << " ";
cout << setfill(’ ‘) << setw(6) << F[i].parent << " ";
cout << setfill(’ ‘) << setw(6) << F[i].lchild << " ";
cout << setfill(’ ') << setw(6) << F[i].rchild << " " << endl;
}
}
5.测试
int main()
{
/*构造赫夫曼树*/
int n;
cout << "请输入赫夫曼树的原始结点数目:" << endl;
cin >> n;
int *a=new int[n];
for (int i = 0; i < n; i++)
{
cin >> a[i];
}
HTree myHTree(a, n);
myHTree.create_HTree();
myHTree.print_HTree();
cout << endl;
/*转二叉链表并先序遍历*/
Node* root = myHTree.to_BiTree(myHTree.getAva()-1);
BiTree myTree2(root);
cout << "赫夫曼树的先序遍历:";
myTree2.pre_traverse_recursion(myTree2.getRoot());
cout << endl;
cout << endl;
/*计算赫夫曼编码,根据结点编号*/
int*b = new int;
int l = myHTree.cal_huffman_code(1, b);
cout <<myHTree.getData(1)<< "的赫夫曼编码是:";
for (int i = l - 1; i >= 0; i--)
cout << *(b + i);
cout << endl;
delete b;
b = NULL;
return 0;
}