一,概述
基本思想:
以字符的使用频率作为权构建棵哈夫曼树,然后利用哈夫曼树对字符进行编码。
如何构造:
每次从树的集合中取出没有双亲且权值最小的两棵树作为左右子树,构造一棵新树,新树根节点的权值为其左右孩子结点权值之和,将新树插入到树的集合中。
二,算法设计
1.确定合适的数据结构:
每个节点的权值,双亲,左孩子,右孩子,以及节点的其他信息。
从叶子节点到根的路径,再求出编码。
树的信息(比如节点个数)
static class Node implements Comparable<Node> {
public String data;//数据信息
public double weight; //权重
public Node left;//左孩子节点
public Node right;//右孩子节点
public Node(String data, double weight){//初始化节点信息,数据和权重
this.data = data;
this.weight = weight;
}
@Override
public String toString(){
return "data:"+this.data+";weight:"+this.weight;
}
@Override
public int compareTo(Node other) {//正序 //排序,按升序排序
if(other.weight > this.weight){
return -1;
}
if(other.weight < this.weight){
return 1;
}
return 0;
}
}
2.初始化:
构造n棵结点为n个字符的单结点树集合
T={t1, t2,… tn},每棵树只有一个带权的根结点,值为该字符的使用频率。
3.如果T中只剩下一棵树,则哈夫曼树构造成功,算法结束。否则,从集合T中取出没有双亲且权值最小的两棵树ti和tj;,将它们合并成一棵新树Zk,新树的左孩子为ti,右孩子为tj,Zk的权值为ti和t j的权值之和。
4.从集合T中删去ti,tj, 加入Zk
5.算法结束。
public static Node createTree(List<Node> nodes){
//只要nodes数组中还有两个以上的节点
int i=1;
while(nodes.size() > 1){
Collections.sort(nodes);//1先排序数据
// 获取权值最小的两个节点
Node left = nodes.get(0); //第一个
Node right = nodes.get(1);//第二个
//生成新节点,新节点的权值为两个子节点的权值之和
Node parent = new Node(null, left.weight+right.weight);
//让新节点作为权值最小的两个节点的父节点
parent.left=left;//较小节点放到左节点
parent.data="t"+(i++);//新节点的名字
parent.right=right;//较大节点放到右节点
//删除权值最小的两个节点
nodes.remove(left);
nodes.remove(right);
//将新生成的节点添加到集合中
nodes.add(parent);
}
return nodes.get(0);
}
递归求解WPL
static int WPL(Node root,int deep,int wpl){
if(root != null){
//当遍历到叶节点则直接输出当前权值*深度
if(root.left==null && root.right==null){
wpl += root.weight * deep;
return wpl;
}else{
wpl += WPL(root.left,deep+1,wpl)+WPL(root.right,deep+1,wpl);
return wpl;
}
}else
return 0;
}
全部代码:
import java.util.*;
public class D {
static class Node implements Comparable<Node> {
public String data;//数据信息
public double weight; //权重
public Node left;//左孩子节点
public Node right;//右孩子节点
public Node(String data, double weight){//初始化节点信息,数据和权重
this.data = data;
this.weight = weight;
}
@Override
public String toString(){
return "data:"+this.data+";weight:"+this.weight;
}
@Override
public int compareTo(Node other) {//正序 //排序,按升序排序
if(other.weight > this.weight){
return -1;
}
if(other.weight < this.weight){
return 1;
}
return 0;
}
}
public static Node createTree(List<Node> nodes){
//只要nodes数组中还有两个以上的节点
int i=1;
while(nodes.size() > 1){
Collections.sort(nodes);//1先排序数据
// 获取权值最小的两个节点
Node left = nodes.get(0); //第一个
Node right = nodes.get(1);//第二个
//生成新节点,新节点的权值为两个子节点的权值之和
Node parent = new Node(null, left.weight+right.weight);
//让新节点作为权值最小的两个节点的父节点
parent.left=left;//较小节点放到左节点
parent.data="t"+(i++);//新节点的名字
parent.right=right;//较大节点放到右节点
//删除权值最小的两个节点
nodes.remove(left);
nodes.remove(right);
//将新生成的节点添加到集合中
nodes.add(parent);
}
return nodes.get(0);
}
//递归求解WPL
static int WPL(Node root,int deep,int wpl){
if(root != null){
//当遍历到叶节点则直接输出当前权值*深度
if(root.left==null && root.right==null){
wpl += root.weight * deep;
return wpl;
}else{
wpl += WPL(root.left,deep+1,wpl)+WPL(root.right,deep+1,wpl);
return wpl;
}
}else
return 0;
}
//哈夫曼树编码,输出为hashmap,key为字符,value为压缩码,此处用到了递归
public static HashMap<String, String>
treeEncode(Node nodeTree,String codeString, HashMap<String, String>hfmmap) {
if(nodeTree!=null)
{
if(nodeTree.left==null&& nodeTree.right==null)
{
hfmmap.put(nodeTree.data, codeString);
}
treeEncode(nodeTree.left, codeString+"0",hfmmap);
treeEncode(nodeTree.right, codeString+"1",hfmmap);
}
return hfmmap;
}
//哈夫曼解码,输入树和压缩码,输出为字符 通过树逆推找到节点
public static String treeDecode (Node nodeTree,String coding) {
for (int i = 0; i < coding.length(); i++) {
if (coding.charAt(i) == '0') {
if (nodeTree.left != null) {
nodeTree = nodeTree.left;
} else {
throw new RuntimeException("编码错误!");
}
}
if (coding.charAt(i) == '1') {
if (nodeTree.right != null) {
nodeTree = nodeTree.right;
} else {
throw new RuntimeException("编码错误!");
}
}
}
return nodeTree.data;
}
//广度优先遍历 层序遍历
public static List<Node> breadth(Node root){
List<Node> list = new ArrayList<Node>();
LinkedList<Node> queue=new LinkedList();
//Queue<Node> queue = new ArrayDeque<Node>();
if(root != null){
//将跟元素入"队列"
queue.offer(root);
}
while(!queue.isEmpty()){
//将该队列的"队尾"的元素添加到list中
list.add(queue.peek());
Node node = queue.pop();//弹出
//如果左节点不为null,将它们加入队列
if(node.left != null){
queue.offer(node.left);
}
//如果有节点不为null,将它们加入队列。
if(node.right != null){
queue.offer(node.right);
}
}
return list;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
List<Node> list = new ArrayList<Node>();
list.add(new Node("a",5));
list.add(new Node("b",32));
list.add(new Node("c",18));
list.add(new Node("d",7));
list.add(new Node("e",25));
list.add(new Node("f",13));
Node root = D.createTree(list);
System.out.println(D.breadth(root));
System.out.println(list);
System.out.println("WPL:"+WPL(root,0,0));
System.out.println("各个节点的哈夫曼编码为:");
HashMap<String, String>hfmmap=new HashMap<String, String>();
Map<String, String>map=treeEncode(root, "", hfmmap) ;
//编码结果
for (String keyString : map.keySet()) {
System.out.print(keyString+":"+map.get(keyString)+";");
}
System.out.println();
System.out.print("输入1000编码,得到其字符为:");
//解码
System.out.print(treeDecode(root, "1000"));
}
}