哈夫曼树
定义
带权路径长度
节点权值乘点到根节点的距离。
哈夫曼树
树上所有节点带权路径长度之和最小的二叉树。
性质
- 满二叉树
- 原序列构成哈夫曼树的所有叶子节点
- 离根节点越近,点权越大
- 非叶子节点的点权之和就是所有叶子节点的带权路径之和
- 哈夫曼树总节点数为 \(2n - 1\),\(n\) 为叶子节点数量
哈夫曼编码
统计每个字母出现次数,以出现次数为权值构建哈夫曼树。
例题
P1090 [NOIP 2004 提高组] 合并果子
实质上是哈夫曼树的构建。
P2168 [NOI2015] 荷马史诗
题意:给定字符串和进制,求:
- \(k\) 叉哈夫曼树带权路径和
- 在合法的哈夫曼树中,最小的深度是多少
分析:
- \(k\) 叉哈夫曼树每 \(k\) 个节点合并成一个节点,若最后一次合并不足 \(k\) 个,说明有节点可以深度更小,带权路径长度可以更小
- 转化成哈夫曼树的根一定要连 \(k\) 条边才最优
- 考虑初始给哈夫曼树补叶子节点,且权值为 \(0\)
- 每 \(k\) 个节点合并成一个节点,相当于每次减少 \(k - 1\) 个节点,因而总节点数 \(n\) 要满足 \(n \equiv 1 (\mod (k - 1))\)
- 当最小权值节点不止 \(2\) 个时,哈夫曼树形态不唯一,此时应该用高度作为排序第二关键字处理合并,最终的高度即可保证最小
建树代码:
cin >> n >> k;
for(int i = 1 ; i <= n ; ++ i)
cin >> x, q.push(Node(x, 1));
while(k != 2 && q.size() % (k - 1) != 1) q.push(Node(0, 1));
while(q.size() >= k) {
int res = 0, tmp = 0;
for(int i = 1 ; i <= k ; ++ i)
res += q.top().x, tmp = max(tmp, q.top().y + 1), q.pop();
ans += res;
q.push(Node(res, tmp));
}
int ansdep = q.top().y - 1;
cout << ans << '\n' << ansdep;
重载代码:
struct Node {
int x, y;
Node(int xx = 0, int yy = 0) {x = xx, y = yy;}
inline bool operator < (const Node &A) const{
if(x == A.x) return y > A.y;
return x > A.x;
}
};
priority_queue<Node> q;