谈谈哈夫曼树
2020-06-18 本文已影响0人
ITsCLG
今天,小编来分享下有关“哈夫曼树”的一些知识点。当然如若有误的话,也请各位江湖好汉指点迷津。在介绍“哈夫曼树”前,我们需要先弄清楚和树有关的3个概念,我们以下图为例子来进行分析。
![](https://img.haomeiwen.com/i19211146/9fe91368c7d502f3.png)
概念1:什么是“路径”和“路径长度”
在一棵树中,从一个结点到另一个结点所经过的所有结点,被我们称为两个结点之间的路径。
例如上述例子中,从根节点A到叶子节点F的路径就为:A->B->D->F;
在一棵树中,从一个结点到另一个结点所经过的“边”的数量,被我们称为两个结点之间的路径长度。
例如,从根节点A到叶子节点F经过的边有3条,因此它的路径长度就为3。
概念2:结点的权及带权路径长度
树的每一个结点,都可以拥有自己的“权重”(Weight),权重在不同的算法当中可以起到不同的作用。
结点的带权路径长度,是指树的根结点到该结点的路径长度,和该结点权重的乘积。
![](https://img.haomeiwen.com/i19211146/02d415bc583a64f6.png)
上图2中结点F的权重是10,从根结点A到结点F的路径长度是3,因此结点F的带权路径长度是 3 X 10= 30。
概念3:树的带权路径长度
在一棵树中,所有叶子结点的带权路径长度之和,被称为树的带权路径长度,也被简称为WPL。
在上图2中,该树的带权路径长度为WPL=1100+250+320+310=290。
那了解了这几个概念后,我们来看看,究竟什么是“哈夫曼树”?
其实啊,所谓的哈夫曼树(Huffman Tree)就是在叶子结点和权重确定的情况下,带权路径长度最小的二叉树,它也被称为最优二叉树。需要注意的是,同样叶子结点所构成的哈夫曼树可能不止一颗哦。
我们以{2,3,7,9,18,25}为例,来构造一棵哈夫曼树。
第一步:构造“森林”
我们把每一个叶子结点,都当做一颗独立的树(只有根结点的树),这样就形成了一个森林:
![](https://img.haomeiwen.com/i19211146/5f31dc15cf609eb9.png)
我们使用队列,根据权值大小存储所有叶子结点。
第二步:选择当前权值最小的两个结点,生成新的父结点。
借助辅助队列,我们可以找到权值最小的结点2和3,并根据这两个结点生成一个新的父结点,父节点的权值是这两个结点权值之和。
![](https://img.haomeiwen.com/i19211146/b2c89e2934e62d38.png)
第三步:从队列中移除上一步选择的两个最小结点,把新的父节点加入队列。也就是从队列中删除2和3,插入5,并且仍然保持队列的升序。
![](https://img.haomeiwen.com/i19211146/9163775fa2a2aff9.png)
第四步:选择当前权值最小的两个结点,生成新的父结点。
这是对第二步的重复操作。当前队列中权值最小的结点是5和7,生成新的父结点权值是5+7=12。
![](https://img.haomeiwen.com/i19211146/194cff4c25f7781e.png)
第五步:从队列中移除上一步选择的两个最小结点,把新的父节点加入队列。
这是对第三步的重复操作,也就是从队列中删除5和7,插入12,并且仍然保持队列的升序。
![](https://img.haomeiwen.com/i19211146/cb40e784114d8000.png)
第六步:选择当前权值最小的两个结点,生成新的父结点。
这是对第二步的重复操作。当前队列中权值最小的结点是9和12,生成新的父结点权值是9+12=21。
![](https://img.haomeiwen.com/i19211146/b3b99f4876454762.png)
第七步:从队列中移除上一步选择的两个最小结点,把新的父节点加入队列。
这是对第三步的重复操作,也就是从队列中删除9和12,插入21,并且仍然保持队列的升序。
![](https://img.haomeiwen.com/i19211146/f0fb52843775c1c7.png)
第八步:选择当前权值最小的两个结点,生成新的父结点。
这是对第二步的重复操作。当前队列中权值最小的结点是18和21,生成新的父结点权值是18+21=39。
![](https://img.haomeiwen.com/i19211146/79fbdd95ccde79fd.png)
第九步:从队列中移除上一步选择的两个最小结点,把新的父节点加入队列。
这是对第三步的重复操作,也就是从队列中删除18和21,插入39,并且仍然保持队列的升序。
![](https://img.haomeiwen.com/i19211146/69f01256acf49823.png)
第十步:选择当前权值最小的两个结点,生成新的父结点。
这是对第二步的重复操作。当前队列中权值最小的结点是25和39,生成新的父结点权值是25+39=64。
![](https://img.haomeiwen.com/i19211146/a772faa2f1207338.png)
第十一步:从队列中移除上一步选择的两个最小结点,把新的父节点加入队列
这是对第三步的重复操作,也就是从队列中删除25和39,插入64。
![](https://img.haomeiwen.com/i19211146/3ced1d52c9108815.png)
此时,队列中仅有一个结点,说明整个森林已经合并成了一颗树,而这棵树就是我们想要的哈夫曼树。
![](https://img.haomeiwen.com/i19211146/16f03e9fe0dec785.png)
下面代码没有使用队列来实现,有兴趣可以自行动手实现,借助C++提供的STL模板等。
#include<iostream>
#include<iomanip>
#include<stack>
using namespace std;
struct element
{
int weight;
int lchild, rchild, parent;
};
class HTree
{
element *node;
int n, m;
stack<int> code;
int setcode(int weight);
public:
HTree(int _n, int _weight[]);
~HTree();
void select(int &s1,int &s2);
void createHT();
int search(int weight);
void print();
void printHTcode();
};
HTree::HTree(int _n, int _weight[])
{
n = _n;
m = 2 * n - 1;//n个元素需要2n-1个空间
node = new element[m];
for (int i = 0; i < n; i++)
{
node[i].weight = _weight[i];
}
for (int i = 0; i < m; i++)
{
node[i].lchild = node[i].rchild = node[i].parent = 0;//初始化为0
}
createHT();
}
HTree::~HTree()
{
delete node;
}
void HTree::select(int &s1, int &s2)
{
for (int i = 0; i < n; i++)
{
if (node[i].parent == 0)
{
s1 = i;//临时元素
break;
}
}
for (int i = 0; i < n; i++)
{
if ((node[i].weight < node[s1].weight) && (node[i].parent == 0))
{
s1 = i;
}
}
cout<<node[s1].weight<<endl;
for (int i = 0; i < n; i++)
{
if ((node[i].parent == 0)&&(i!=s1))
{
s2 = i;
break;
}
}
for (int i = 0; i < n; i++)
{
if ((node[i].weight < node[s2].weight) && (node[i].parent == 0) && (i != s1))
s2 = i;
}
cout<<node[s2].weight<<endl;
}
void HTree::createHT()
{
for (int i = n; i < m; i++)
{
int s1, s2;//左小右大
select(s1,s2);
node[s1].parent = i;
node[s2].parent = i;
node[i].lchild = s1;
node[i].rchild = s2;
node[i].weight = node[s1].weight + node[s2].weight;
n++;
}
}
int HTree::setcode(int weight)
{
int location, parent, k;//k为移动指针
int count = 0;
location = search(weight);
k = location;
parent = node[location].parent;
while ((node[k].parent != 0) && (k != -1))
{
if (node[parent].lchild == k)
{
code.push(0);
}
else
{
code.push(1);
}
k = node[k].parent;
parent = node[k].parent;
count++;
}
if (k == -1)
cout << "未找到!" <<endl;
return count;
}
int HTree::search(int weight)
{
int result = -1;
for (int i = 0; i < m; i++)
{
if (node[i].weight == weight)
{
result = i;
}
}
return result;
}
void HTree::print()
{
cout << "索引 权值 双亲 左孩子 右孩子" << endl;
cout << left;//左对齐
for (int i = 0; i < m; i++)
{
cout << setw(5) << i << " ";
cout << setw(6) << node[i].weight << " ";
cout << setw(6) << node[i].parent << " ";
cout << setw(6) << node[i].lchild << " ";
cout << setw(6) << node[i].rchild << endl;
}
cout << "哈夫曼树打印完毕"<<endl;
}
void HTree::printHTcode()
{
cout << "请输入要查询的编码的权值" << endl;
int weight, size;
cin >> weight;
size = setcode(weight);
cout << "权值" <<weight<< "编码长度" <<size<< endl;
cout << "编码结果为";
for (int i = 0; i < size; i++)
{
cout << code.top()<<" ";
code.pop();
}
cout << endl;
}
int main()
{
cout << "请输入要构造的哈夫曼树的结点个数" <<endl;
int n, *weight;
cin >> n;
weight = new int[n+1];
cout << "请输入这" << n << "个元素的权值" << endl;
for (int i = 0; i < n; i++)
{
cin >> weight[i];
}
HTree test(n, weight);
test.print();
test.printHTcode();
system("pause");
return 0;
}
运行截图:
![](https://img.haomeiwen.com/i19211146/8cd5f063b25a8109.png)