先从思路入手 ,如何构造一颗haffmantree ?
首先就要先了解哈夫曼树是怎么构建的,哈夫曼树的构建不是从顶向下的而是自下而上的,即先确定叶子结点最后才生成根节点。
第一步:将所有的结点按照权值的大小先排好序如下图所示(可以用优先级队列来实现这个排序的结点,这样每次要取最小的两个结点的时候只要出队就行,插入结点的时候就一个入队的操作)
第二步:依次取两个最小权值的结点生成一颗树他们的父节点的权值为这两个结点的和,从排好序的结点中删除这俩个结点,最后再将父节点插入原来排好序的结点中。
第三步:重复第一步和第二步直到排好的序的结点数为1的时候此时的哈夫曼树就完成了,最后一个结点即为树的根节点
树构造完毕后就是编码的生成
编码的思想就是使得出现频率次数越高的值,生成的二进制数的位数就越少,从而达到压缩的目的。
如图所示就是从根节点往下找要编码的结点,向左边就赋值为0,向右边则赋值为1,直到找到要编码的结点即结束。
代码如下
优先级列实现结点的排序
package com.yc.Huffman;
/**
* 优先级队列
* 这里采用的是有序数组的方式来实现优先级队列
* 更优化的方式其实可以采用有序链表来实现优先级队列因为大多的有序数组都可以用有序队列来实现替换
* 且性能会更加的好(减少了大量的交换次数)
*
* @author 29226
*
*/
public class priorityQu {
public int maxsize;
public Node[] quArray;
public int nitem;//元素指针
//初始化
public priorityQu(int size) {
maxsize=size;
quArray=new Node[maxsize];
nitem=0;
}
//入队的方法
public void insert(Node node) {
int j=0;
if(nitem==0) {
//队列没有元素直接加入第一个元素
quArray[nitem++]=node;
}else {
//要插入的位置在队列中间
for(j=nitem-1;j>=0;--j) {
if(node.priority>quArray[j].priority) {
quArray[j+1]=quArray[j];
}else {
//找到了要插入的位置
break;
}
}
//因为到了要插入的位置的时候j 已经指向了前面一个元素
//要插入的位置在j的后面一个的位置
quArray[j+1]=node;
++nitem;
}
}
//移除元素的方法(每次移除的都是最小值)
public Node remove() {
Node temp=quArray[--nitem];
quArray[nitem]=null;
return temp;
}
//队列是否为空
public boolean isEmpty() {
if(nitem==0) {
return true;
}else {
return false;
}
}
//返回元素个数
public int size() {
return nitem;
}
}
结点类
package com.yc.Huffman;
/**
* 结点类
* @author 29226
*
*/
public class Node {
public int priority;//优先级(出现的频率)
public String data;//数据
public Node leftchild;
public Node rightchild;
@Override
public String toString() {
return "Node [priority=" + priority + ", data=" + data + ", leftchild=" + leftchild + ", rightchild="
+ rightchild + "]";
}
}
huffmanTree
package com.yc.Huffman;
import java.util.HashMap;
import java.util.Map;
/**
* 哈夫曼树
* @author 29226
*
*/
public class HuffmanTree {
public Node root;//头结点
public Map<String ,String > codes=new HashMap<>();//用于存放编码的
//通过构造函数生成HuffmanTree
public HuffmanTree(priorityQu qu) {
//每次从队列中取出两个元素(小的)结点生成一个新的结点
Node t;
while(qu.size()>1) {
Node left=qu.remove();
Node right=qu.remove();
Node parent=new Node();
parent.priority=left.priority+right.priority;
parent.leftchild=left;
parent.rightchild=right;
//parent.data=left.data+"和"+right.data+"的合成树";
qu.insert(parent);//将新的结点插入队列
root=parent;
}
}
//编码(其实就是在先序遍历的时候加已记录路径)
public void findAllLeaf(Node node,String suffx) {
if(node !=null) {
//如果没有左右子树则说明是叶子结点了要将记录的编码存放到map中
if(node.leftchild==null&&node.rightchild==null) {
codes.put(node.data,suffx);
}
//向左边走记录值加0
findAllLeaf(node.leftchild,suffx+"0");
//向右边走记录值加1
findAllLeaf(node.rightchild,suffx+"1");
}
}
//前序遍历算法
public void prodisplay(Node node ) {
if(node!=null) {
System.out.print(node.data+" ");
prodisplay(node.leftchild);
prodisplay(node.rightchild);
}
}
//中序遍历算法
public void display(Node node ) {
if(node!=null) {
display(node.leftchild);
if(node.data!=null) {
System.out.print(node.data+" ");
}else {
System.out.print(" ");
}
display(node.rightchild);
}
}
//后序遍历算法
public void fdisplay(Node node ) {
if(node==null) {
return;
}
fdisplay(node.leftchild);
fdisplay(node.rightchild);
if(node.data!=null) {
System.out.print(node.data+" ");
}else {
System.out.print(" ");
}
}
}
测试方法
package com.yc.Huffman;
/**
* 测试类
* @author 29226
*
*/
public class HuffmanApp {
public static void main(String[] args) {
/**
* A B C D E F G data
* 2 1 3 5 6 2 8 priority
*/
String [] data= {"A","B","C","D","E","F","G"};
int [] priority= {2,1,3,5,6,2,8};
priorityQu qu=new priorityQu(data.length*2);
for(int i=0,n=data.length;i<n;++i) {
Node item=new Node();
item.data=data[i];
item.priority=priority[i];
qu.insert(item);
}
HuffmanTree huffmantree=new HuffmanTree(qu);
//先序遍历
huffmantree.prodisplay(huffmantree.root);
//编码
huffmantree.findAllLeaf(huffmantree.root, "");
System.out.println();
//取值
System.out.println(huffmantree.codes);
}
}
最后附加一张运行图和一张测试的树的图(树的图是自己画的有点丑各位别介意!🤭)
此处的null是因为两个最小的结点生成的父节点的时候只给父节点赋了权值而没有数据值