哈夫曼树的求法,代码实现及证明,图文解释

哈夫曼树的定义

  • 路径和路径长度
    在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为 1,则从根结点到第 L 层结点的路径长度为 L−1。
  • 结点的权及带权路径长度
    若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。
  • 树的带权路径长度
    树的带权路径长度规定为所有叶子结点(不是所有节点!)的带权路径长度之和,记为 WPL。
    哈夫曼树的求法,代码实现及证明,图文解释_第1张图片

如上图的树的带权路径长度为7*3 + 2*2 + 5*1 = 30

我们知道给定任意叶节点,用不同方式构造出的二叉树,其带权路径长度往往不同,而哈夫曼树就是这些节点构造出的二叉树带权路径长度最小的情况

哈夫曼树的求法

如果初次接受这种解法可能觉得莫名其妙,后面会结合代码对求法给出解释,让你信服这种解法

哈夫曼树的构造采取的是一种贪心策略

  1. 将所有节点放入优先队列中。
  2. 提出最小的两个节点将其合并,它们的父节点权值为提取的两个节点权值之和,将它们的父节点重新放入优先队列,循环往复,知道只剩下一个节点
  3. 该节点即为所求的哈夫曼树

解法图示:

构造出这个树以后,如果要求最短树的带权路径长度,不仅可以直接用之前的方法,还可以直接求合并节点的权值和,即上面红色数字的和,即4+8+13=25。原理大家想一下应该就能明白,我就不多说了。这个计算方法不仅是代码的关键,也是之后证明的关键。

求最短树的带权路径长度代码实现

传送门:acwing 哈夫曼树
题目如下
在这里插入图片描述
哈夫曼树的求法,代码实现及证明,图文解释_第2张图片

#include
#include
using namespace std;

int main(){
    int n;
    int res = 0;//结果
    cin>>n;
    priority_queue<int, vector<int>, greater<int>> q;
    while(n--){
        int num;
        cin>>num;
        q.push(num);
    }
    //当队列中只有一个节点就结束循环
    while(q.size()>1){
        int x = q.top();
        q.pop();
        int y = q.top();
        q.pop();
        int sum = x+y;//父节点权值
        q.push(sum);//将父节点放入优先队列中
        res += sum;//将合并的节点权值加到res中,就是上面我所说的“关键”
    }
    cout<<res<<endl;
    return 0;
}

如果是构建哈夫曼树的话把权值换成节点就可以了

求法的解释

其实如果你看懂为啥上面所有合并节点的权值之和就是最终结果,你应该就知道为什么可以这样做了。因为每次循环我都要加上一个合并出的节点权值,这个节点还要重新放入队列中等待下次合并,当然是从开始拿的越小越好。可能有些人看的可能有点蒙…hh,还是上面我说的,搞懂为啥所有合并节点的权值之和就是最终结果你就清楚了,如果实在搞不懂,这个方法记住就可以啦~

你可能感兴趣的:(算法,数据结构,算法)