//主要给自己看的,不会写的那么详细。
在洛谷-深入浅出这本书中的贪心这一节,有提到所谓的哈夫曼编码,后续了解到其与一种特殊的数据结构“哈夫曼树”,又被称为最优二叉树紧密相连。深入浅出这本书并未对其进行详细解释,所以自学了。
一些基本概念
路径:一个结点到另一个结点所经过的结点,比如A到D的路径就是ABD
路径长度: 一个结点到另一个结点所经过的边的数量被称为路径长度,比如A到D了的路径长度为2
结点带权路径长度:指一个结点的权数 乘以 该节点到根节点的路径长度。
WPL(树的带权路径长度):指树的所有叶子结点的带权路径长度之和,叶子结点是指没有孩子结点的结点。也可以通过将所有的非根节点的权值累加起来计算出WPL。
拥有以上的基本知识后,我们可以简单介绍一下哈夫曼树了,哈夫曼树指的就是,WPL最小的二叉树,最优二叉树。
原则上,我们需要将叶子结点权重较小的远离树根,权重较大的靠近树根,这样便可以构建出一个哈夫曼树。
如何将一串数据,构建成哈夫曼树呢,对于C++,我们可以采用优先队列(priority_queue)的方式,将权重最小的两个元素先排好,较小的那个元素是左节点,较大的那个元素是右节点,然后这两个节点的父节点的权重就是这两个元素之和,再以此类推,直到构建完毕。
通过构建哈夫曼树,我们可以很容易的求出最小WPL。
例题
P1090 [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
例如有 33 种果子,数目依次为 1, 2 , 9 。可以先将 1 、 2 堆合并,新堆数目为 3 ,耗费体力为 3 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 12 ,耗费体力为 12 。所以多多总共耗费体力 =3+12=15 。可以证明 15 为最小的体力耗费值。
此题标记为贪心题,但是我们需要知道该如何贪,如果我们对哈夫曼树有一些基本的了解,我们便可以知道,将果子的数目进行排序,构建出哈夫曼树,便可以求出最小WPL,也就是该题所说的最小的体力耗费值。这里我们采用了将所有的非根节点累加起来的策略计算出最终值。
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <queue>
using namespace std;
int main()
{
priority_queue<int,vector<int>,greater<int>> h;
int n,res=0; cin >> n;
for (int i = 0; i < n; i++)
{
int num;
cin >> num;
h.push(num);
}
while (h.size()>1)
{
int sum = 0;
sum += h.top(); h.pop();
sum += h.top(); h.pop();
h.push(sum);
res += sum;
}
cout << res;
return 0;
}