一、哈夫曼树概述
- 哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。
- 所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。
- 树的路径长度是从树根到每一结点的路径长度之和,记为WPL=(W1L1+W2L2+W3L3+…+WnLn),N个权值Wi(i=1,2,…n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,…n)。
- 哈夫曼树的WPL是最小的。
二、哈夫曼树的特性
哈夫曼树又称为最优树:
-
路径和路径长度
在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1。 -
结点的权及带权路径长度
若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。 -
树的带权路径长度
树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL。
三、构造哈夫曼树
定义哈夫曼树结构
typedef struct HuffmanTree { //创建哈夫曼树结构;
int weight; //存放权值;
int prt, lcd, rcd; //定义父结点,左孩子,右孩子;
}HfNode, *HfTree;
创建哈夫曼树
void CreateHuffmanTree(HfTree &HT, int n) //创建哈夫曼树;
{
if (n <= 1) return;
int m = 2 * n - 1; //设置最大结点数;
HT = new HfNode[m + 1]; //动态分配m+1个结点单元,因为0号结点没有使用,所以+1;
for (int i = 1; i <= m; i++) //初始化所有结点的父结点、左孩子、右孩子为0;
{
HT[i].lcd = HT[i].rcd = HT[i].prt = 0;
}
cout << "请输入需要构造哈夫曼树的序列:";
for (int i = 1; i <= n; i++) //输入前n个结点的权值;
{
cin >> HT[i].weight;
}
for (int i = n + 1; i <= m; i++) //通过n-1次操作来创建哈夫曼树;
{
int s1, s2; //声明两个存放最小结点位置的变量;
select(HT, s1, s2, i); //调用select函数,将选择的最小值赋给s1、s2;
HT[i].lcd = s1;
HT[i].rcd = s2; //将s1,s2所在位置的结点分别当作当前结点的左右孩子;
HT[s1].prt = HT[s2].prt = i; //更改s1,s2所在位置结点的父结点为当前结点;
HT[i].weight = HT[s1].weight + HT[s2].weight;//当前结点的权值等于左右孩子权值之和;
}
}
PS:在创建哈夫曼树的过程中有一个选择函数,必须从小到大选则,刚开始我是打算只遍历一遍找出最小的两个结点位置的,但是会出现很多错误,一直没能解决,最后还是用了遍历两次的方法。
四、BFS 遍历哈夫曼树
void BFS_Print(HfNode* HT, int m, int n) //BFS输出哈夫曼树;
{
int t = 0;
int b[50] = { 0 };
queue <int> q;
q.push(m);
cout << " ";
while (!q.empty()) //遍历队列;
{
if (b[q.front()] != t) { //为了让输出显得更有层次;
t += 1;
cout << endl;
for (int i = n-t; i >= 0; i--)
cout << " ";
}
if (b[q.front()] == t)
cout << HT[q.front()].weight << " ";
/*判断该结点有左孩子和右孩子吗,有的话将其入队,没有的话置空*/
HT[q.front()].lcd != 0 ? q.push(HT[q.front()].lcd) : NULL;
HT[q.front()].rcd != 0 ? q.push(HT[q.front()].rcd) : NULL;
b[HT[q.front()].lcd] = b[HT[q.front()].rcd] = t + 1;
q.pop(); //记得将队首出队;
}
cout << endl;
}
五、完整代码
#include <iostream>
#include <queue>
using namespace std;
/*
8
5 29 7 8 14 23 3 11
*/
typedef struct HuffmanTree { //创建哈夫曼树结构;
int weight; //存放权值;
int prt, lcd, rcd; //定义父结点,左孩子,右孩子;
}HfNode, *HfTree;
void select(HfTree &HT, int &s1, int &s2, int m) //按顺序查找当前树结点中最小的两个结点
{
int x = 0xcfffff;
int y = x + 1;
for (int i = 1; i < m; i++)
{
if (!HT[i].prt) {
if (x > HT[i].weight) {
x = HT[i].weight;
s1 = i;
}
}
}
for (int i = 1; i < m; i++)
{
if (!HT[i].prt) {
if (y > HT[i].weight && (HT[i].weight > x||HT[i].weight == x && i != s1)) {
y = HT[i].weight;
s2 = i;
}
}
}
}
void BFS_Print(HfNode* HT, int m, int n) //BFS输出哈夫曼树;
{
int t = 0;
int b[50] = { 0 };
queue <int> q;
q.push(m);
cout << " ";
while (!q.empty()) //遍历队列;
{
if (b[q.front()] != t) { //为了让输出显得更有层次;
t += 1;
cout << endl;
for (int i = n-t; i >= 0; i--)
cout << " ";
}
if (b[q.front()] == t)
cout << HT[q.front()].weight << " ";
/*判断该结点有左孩子和右孩子吗,有的话将其入队,没有的话置空*/
HT[q.front()].lcd != 0 ? q.push(HT[q.front()].lcd) : NULL;
HT[q.front()].rcd != 0 ? q.push(HT[q.front()].rcd) : NULL;
b[HT[q.front()].lcd] = b[HT[q.front()].rcd] = t + 1;
q.pop(); //记得将队首出队;
}
cout << endl;
}
void CreateHuffmanTree(HfTree &HT, int n) //创建哈夫曼树;
{
if (n <= 1) return;
int m = 2 * n - 1; //设置最大结点数;
HT = new HfNode[m + 1]; //动态分配m+1个结点单元,因为0号结点没有使用,所以+1;
for (int i = 1; i <= m; i++) //初始化所有结点的父结点、左孩子、右孩子为0;
{
HT[i].lcd = HT[i].rcd = HT[i].prt = 0;
}
cout << "请输入需要构造哈夫曼树的序列:";
for (int i = 1; i <= n; i++) //输入前n个结点的权值;
{
cin >> HT[i].weight;
}
for (int i = n + 1; i <= m; i++) //通过n-1次操作来创建哈夫曼树;
{
int s1, s2; //声明两个存放最小结点位置的变量;
select(HT, s1, s2, i); //调用select函数,将选择的最小值赋给s1、s2;
HT[i].lcd = s1;
HT[i].rcd = s2; //将s1,s2所在位置的结点分别当作当前结点的左右孩子;
HT[s1].prt = HT[s2].prt = i; //更改s1,s2所在位置结点的父结点为当前结点;
HT[i].weight = HT[s1].weight + HT[s2].weight;//当前结点的权值等于左右孩子权值之和;
}
}
int main()
{
int n;
HfTree HT;
cout << "输入叶子结点的个数 n:";
cin >> n;
CreateHuffmanTree(HT, n); //创建哈夫曼树;
BFS_Print(HT, 2*n-1, n); //BFS输出哈夫曼树;
return 0;
}
蒟蒻一只,欢迎指正