目录
简介
哈夫曼树又被称为最优二叉树,是一类带权路径最短的二叉树。
哈夫曼树是二叉树的一种应用,在信息检索中很常用。
相关概念
- 节点之间的路径长度:从一个节点到另一个节点之间的分支数量称为两个节点之间的路径长度。
- 树的路径长度:从根节点到树中每一个节点的路径长度之和。
- 节点的带权路径长度:从该节点到根节点之间的路径长度与节点上权的乘积。
- 树的带权路径长度:树中所有叶子节点的带权路径长度之和。
哈夫曼树实现
实现原理:
- 根据给定的n个权值{w1,w2,...,wn}构造n棵二叉树的集合F={T1,T2,...,Tn},F集合中每棵二叉树都只有一个根节点。
- 选取F集合中两棵根节点的权值最小的树作为左、右子树以构造一棵新的二叉树,且将新的二叉树的根节点的权值设为左、右子树上根节点的权值之和。
- 将新的二叉树加入到F集合中,并删除(2)步中被选中的两棵树。
- 重复(2)和(3)步直到F集合中只剩下一棵树,这棵树就是哈夫曼树。
实现代码:
public class HuffmanTree {
public static class Node<E> {
E data;
double weight;
Node leftChild;
Node rightChild;
public Node(E data, double weight) {
this.data = data;
this.weight = weight;
}
@Override
public String toString() {
return "Node[data=" + data + ",weight=" + weight + "] \n";
}
}
public static void main(String[] args) {
List<Node> nodes = new ArrayList<>();
nodes.add(new Node("A", 40.0));
nodes.add(new Node("B", 8.0));
nodes.add(new Node("C", 10.0));
nodes.add(new Node("D", 30.0));
nodes.add(new Node("E", 10.0));
nodes.add(new Node("F", 2.0));
Node root = HuffmanTree.createTree(nodes);
System.out.println(breadthFirst(root));
}
/**
* 构造哈夫曼树
*
* @param nodes 节点集合
* @return 构造出来的哈夫曼树的根节点
*/
private static Node createTree(List<Node> nodes) {
// 只要nodes数组中还有两个以上的节点
while (nodes.size() > 1) {
quickSort(nodes);
// 获取权值最小的两个节点
Node left = nodes.get(nodes.size() - 1);
Node right = nodes.get(nodes.size() - 2);
// 生成新节点,新节点的权值为两个子节点的权值之和
Node parent = new Node(null, left.weight + right.weight);
// 让新节点作为权值最小的两个节点的父节点
parent.rightChild = right;
parent.leftChild = left;
// 删除权值最小的两个节点
nodes.remove(nodes.size() - 1);
nodes.remove(nodes.size() - 1);
//将生成的父节点添加到集合中
nodes.add(parent);
}
return nodes.get(0);
}
private static void quickSort(List<Node> nodes) {
subSort(nodes, 0, nodes.size() - 1);
}
// 快速实现排序算法,用于对节点进行排序
private static void subSort(List<Node> nodes, int start, int end) {
// 需要排序
if (start < end) {
// 以第一个元素作为分界值
Node base = nodes.get(start);
// i从左边搜索,搜索大于分界值的元素的索引
int i = start;
// j从右边开始搜索,搜索小于分界值的元素的索引
int j = end + 1;
while (true) {
// 找到大于分界值的元素的索引,或者i已经到了end处
while (i < end && nodes.get(++i).weight >= base.weight);
// 找到小于分界值的元素的索引,或者j已经到了start处
while(j > start && nodes.get(--j).weight <= base.weight);
if (i < j) {
swap(nodes, i, j);
} else {
break;
}
}
swap(nodes, start, j);
// 递归左边子序列
subSort(nodes, start, j - 1);
// 递归右边子序列
subSort(nodes,j + 1, end);
}
}
private static void swap(List<Node> nodes, int i, int j) {
Node tmp = nodes.get(i);
nodes.set(i, nodes.get(j));
nodes.set(j, tmp);
}
public static List<Node> breadthFirst(Node root) {
Queue<Node> queue = new ArrayDeque<>();
List<Node> list = new ArrayList<>();
if (root != null) {
queue.offer(root);
}
while (!queue.isEmpty()) {
list.add(queue.peek());
Node p = queue.poll();
if (p.leftChild != null) {
queue.offer(p.leftChild);
}
if (p.rightChild != null) {
queue.offer(p.rightChild);
}
}
return list;
}
}
代码实现步骤:
- 对List集合中所有节点进行排序。
- 找出List集合中权值最小的两个节点。
- 以权值最小的两个节点作为子节点创建新节点。
- 从List集合中删除权值最小的两个节点,将新节点添加到List集合中。