什么是哈夫曼树 ?
给定n个权值作为n个叶子节点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树。哈夫曼树是带权路径长度最短的树,权值较大的节点距离根节点较近。
基本术语
- 路径和路径长度
在一棵树中,从一个节点往下可以达到的孩子或孙子节点之间的通路,称为路径。通路中分支的数目称为路径长度。
- 节点的权值及带权路径的长度
若将树中节点赋一个有着某种含义的数值,则这个数值称为该节点的权( 哈夫曼树中的权通常指节点出现的次数/频率 ),节点的带权路径长度为:从根节点到该节点之间的路径长度与该节点的权的乘积。
- 树的带权路径长度
树的带权路径长度规定为:所有叶子节点的带权路径长度之和,记为WPL。
情景:
考虑这样一个问题,现在我们要对学生的考试成绩进行统计,考试成绩的等级有 A、B、C、D 四个等级,处于四个等级中的学生人数分别为10、50、60、5人。要编写这样一个代码,其实是非常easy的,只需要用if、else即可。
代码一:
if(A){
}else if(B){
}else if(C){
}else {
}
执行次数:10 + 50*2 + 60*3 + 5*3 = 305(次)
代码二:
if(B){
}else if(C){
}else if(D){
}else {
}
执行次数:50 + 60*2 + 5*3 + 10*3 = 215
可以发现代码一和代码二的结构是一样的,但是两份代码的执行次数却差了将近100次,如果我们想寻找一种最优化的方案,那么我们又该如何排序呢?或许二叉树可以办到呢!
哈夫曼树的实现步骤:
- 统计字符串中的字符以及出现的次数,将节点存放在数组队列中
- 按照节点的权值排序
- 取出并删除权值最小的两节点,这两个节点的父节点的数据为两个节点的数据相加,权值为两节点的权值相加,将两个节点构建为新节点。将节点放回数组队列中并排序(保证节点的权值有序)
- 重复步骤3
Example:
根据字符串 aaabbccccddddddee 构建哈夫曼树
节点 权值
e 2
b 2
a 3
c 4
d 6
节点 权值
a 3
eb 4
c 4
d 6
重复上述步骤知道数组队列中只有一个节点。
注意:
哈夫曼树的结构不唯一,但是树的带权路径长度是唯一的。
代码实现
Node类
public class Node {
Node left,right;
String data;
int weight;
public Node(){}
public Node(String data){
this.data = data;
}
public void setLeft(Node left){
this.left = left;
}
public Node getLeft(){
return left;
}
public void setRight(Node right){
this.right = right;
}
public Node getRight(){
return right;
}
public void setWeight(int weight){
this.weight = weight;
}
public int getWeight(){
return weight;
}
public void setData(String data){
this.data = data;
}
public String getData(){
return data;
}
}
Tree类
public class HalfTree {
static Node root;
String s = "";
//用来存放节点和权值
ArrayList<Node> list = new ArrayList<>();
public void create_tree(String str){
//统计字符及出现的次数
int[] count = new int[128];
for(int i=0;i<str.length();i++){
char ch = str.charAt(i);
count[ch]++;
}
for(int i=0;i<count.length;i++){
if(count[i]!=0){
char ch = (char) i; //强制转换为char类型
int number = count[i];
Node node = new Node(ch+"");
node.weight = number;
list.add(node);
}
}
while(list.size()>1){
//根据权值进行排序
this.sort();
Node left = list.remove(0);
Node right = list.remove(0);
String s = left.data + right.data;
Node father = new Node(s);
father.setWeight(left.weight+right.weight);
father.setLeft(left);
father.setRight(right);
list.add(0,father);
}
root = list.get(0);
}
//根据节点的权值从小到大进行排序
public void sort(){
Node temp;
for(int i=0;i<list.size();i++){
for(int j=i+1;j<list.size();j++){
if(list.get(i).weight > list.get(j).weight){
temp = list.get(i);
list.set(i, list.get(j));
list.set(j, temp);
}
}
}
}
//先序遍历(根--->左--->右)
public void before_output(Node node){
if(node==null){
return ;
}else {
System.out.println(node.getData()+" "+node.getWeight());
before_output(node.getLeft());
before_output(node.getRight());
}
}
//中序遍历(左---根--->右 )
public void middle_output(Node node){
if(node.getLeft()!=null){
middle_output(node.getLeft());
}
System.out.println(node.getData()+" "+node.getWeight());
if(node.getRight()!=null){
middle_output(node.getRight());
}
}
//后序遍历(左--->右--->根)
public void behind_output(Node node){
if(node.getLeft()!=null){
behind_output(node.getLeft());
}
if(node.getRight()!=null){
behind_output(node.getRight());
}
System.out.println(node.getData()+" "+node.getWeight());
}
public static void main(String[] args){
HalfTree tree = new HalfTree();
tree.create_tree("aaabbccccddddddee");
System.out.println("先序遍历:");
tree.before_output(root);
System.out.println("----------------");
System.out.println("中序遍历:");
tree.middle_output(root);
System.out.println("----------------");
System.out.println("后序遍历:");
tree.behind_output(root);
}
}
样例输出:
先序遍历:
abecd 17
abe 7
a 3
be 4
b 2
e 2
cd 10
c 4
d 6
----------------
中序遍历:
a 3
abe 7
b 2
be 4
e 2
abecd 17
c 4
cd 10
d 6
----------------
后序遍历:
a 3
b 2
e 2
be 4
abe 7
c 4
d 6
cd 10
abecd 17
可承接各种项目,有意者加QQ:1217898975