哈夫曼树以及哈夫曼编码
一、问题描述
构造一颗包含个叶子节点的叉树,其中第个叶子节点带有权值,要求最小化,其中表示第个叶子节点到根节点的距离。
二、算法描述
运用贪心的思想,权值大的叶子结点的深度一定要小。
先考虑的情况:
我们不难想出一种贪心算法。
1.建立一个小根堆,插入这个叶子节点的权值。
2.从堆中取出最小的两个权值和,令。
3.建立一个新的节点,权值为,令成为取出的两个节点的父亲。
4.在堆中插入权值。
5.重复上述到步,直到堆得大小为。
Luogu P1090 合并果子
点击查看代码
import java.util.ArrayList;
import java.util.PriorityQueue;
import java.util.Scanner;
/**
* @author dongyudeng
*/
public class BinaryHuffmanTree {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int num = scanner.nextInt();
ArrayList<Integer> values = new ArrayList<>();
for (int i = 0; i < num; i++) {
values.add(scanner.nextInt());
}
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(values);
Integer ans = 0;
while (priorityQueue.size() > 1) {
Integer value1 = priorityQueue.poll(), value2 = priorityQueue.poll();
ans += value1 + value2;
priorityQueue.add(value1 + value2);
}
System.out.println(ans);
}
}
接着再扩展到的情况:
我们的第一个想法必定是套用上述贪心算法,只是改为每次从堆中取出最小的个值。但是在最后一轮循环中,堆的大小在之间,根节点的子节点数小于,显然不是最优解,因此我们补加一些权值为的叶子节点,使得叶子节点的个数满足,这么做是为了让子节点数小于的情况出现在最底层(权值为的叶子结点显然是在最底层的),从而保证正确性。
注:
推导:
设度为的节点数为,总节点数为。
补了权值为的节点后,除了叶子结点外的所有节点都满度,所以。
又因为(入边出边),可得,得证。
哈夫曼树的应用:哈夫曼编码
Luogu P2168 荷马史诗
本题所构建的即为哈夫曼编码,我们将个值作为叶子结点的权值,求出哈夫曼树,将每个节点的出边标上到,即可构成一颗树。
观察这颗树,单词的编码为从根节点到叶子结点的路径,又因为单词都是叶子结点,恰好满足了一个单词不是另一个的前缀。
此外,本题要求的深度最小,我们在合并时优先合并已合并次数最少的节点即可。
点击查看代码
import java.util.ArrayList;
import java.util.Objects;
import java.util.PriorityQueue;
import java.util.Scanner;
/**
* @author dongyudeng
* Luogu P2168 荷马史诗
*/
public class HuffmanTree {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int num = scanner.nextInt(), k = scanner.nextInt();
ArrayList<Long> values = new ArrayList<>();
for (int i = 0; i < num; i++) {
values.add(scanner.nextLong());
}
while ((num - 1) % (k - 1) != 0) {
values.add(0L);
num++;
}
PriorityQueue<TreeNode> priorityQueue = new PriorityQueue<>();
for (Long value : values) {
priorityQueue.add(new TreeNode(0, value));
}
long ans = 0;
while (priorityQueue.size() >= k) {
TreeNode[] nodes = new TreeNode[k];
for (int i = 0; i < k; i++) nodes[i] = priorityQueue.poll();
int maxCnt = 0;
long value = 0;
for (TreeNode node : nodes) {
value += node.getValue();
maxCnt = Math.max(maxCnt, node.getCnt());
}
ans += value;
priorityQueue.add(new TreeNode(maxCnt + 1, value));
}
System.out.println(ans);
int maxCnt = 0;
for (TreeNode node : priorityQueue) maxCnt = Math.max(maxCnt, node.getCnt());
System.out.println(maxCnt);
}
}
class TreeNode implements Comparable<TreeNode> {
private int cnt;
private long value;
public TreeNode(int cnt, long value) {
this.cnt = cnt;
this.value = value;
}
public int getCnt() {
return cnt;
}
public void setCnt(int cnt) {
this.cnt = cnt;
}
public long getValue() {
return value;
}
public void setValue(long value) {
this.value = value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TreeNode treeNode = (TreeNode) o;
return cnt == treeNode.cnt && value == treeNode.value;
}
@Override
public int hashCode() {
return Objects.hash(cnt, value);
}
@Override
public int compareTo(TreeNode o) {
//equal
if (this.value == o.value && this.cnt == o.cnt) return 0;
//less or greater
boolean isLess = this.value < o.value || (this.value == o.value && this.cnt < o.cnt);
if (isLess) return -1;
else return 1;
}
}
扩展:
关于Luogu P6033 合并果子(加强版):
桶排序+队列模拟(java过不了)。
代码地址:
码云
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2020-06-04 P4297 [NOI2006]网络收费
2020-06-04 P4207 [NOI2005]月下柠檬树
2020-06-04 bzoj2517 矩形覆盖
2020-06-04 bzoj2506 calc
2020-06-04 越野赛车问题
2020-06-04 luoguP5385 [Cnoi2019]须臾幻境