一、内容
1、内部类
哈夫曼树节点类型:HuTNode.class
封装节点在底层数组的下标和对应的权重的类:IndexAndWeight.class
封装字符和对应哈夫曼编码的类:HuCode.class
2、方法
构造哈夫曼树:CreatHuTree()
在合并节点时,选择权重最小和次小的两个节点:selectIndexOfMinWeight(HuTNode ht)
根据构造的哈夫曼树,获取叶子节点对应字符的哈夫曼编码:getHuCode(HuTNode ht)
根据得到的哈夫曼编码集,将字符串转换成0101串:String2HuffmanCode(String source, String huffmanCode)
根据得到的哈夫曼编码集,将0101串转换成字符串:HuffmanCode2String(String huffmanCode, String source)
二、代码
public class HuTree {
public static void main(String[] args) {
// 构建哈夫曼树
HuTNode[] ht = creatHuTree();
// 输出构造好的哈夫曼树
System.out.println("\n===============输出构造好的哈夫曼树===================");
System.out.println("下标\t\t数据\t\t权重\t\t双亲\t\t左孩\t\t右孩");
System.out.println("0\t\t-\t\t-\t\t-\t\t-\t\t-\t\t");
for (int i=1; i<ht.length; ++i){
System.out.print(i + "\t\t");
System.out.println(ht[i]);
}
// 根据哈夫曼树ht,将每个给定字符及其哈夫曼编码存储到一个HuCode类型数组中
HuCode[] hc = getHuCode(ht);
// 输出哈夫曼编码集hc
System.out.println("\n===============哈夫曼编码集===================");
System.out.println("数据\t\t哈夫曼码");
for (HuCode code: hc){
System.out.println(code);
}
// 给定字符串,根据上述哈夫曼编码转换成0101串
String source = "A_Tree";
String huffmanCode = String2HuffmanCode(source, hc);
System.out.println("\n===============【A_Tree】对应的0101串===================");
System.out.println(huffmanCode);
// 解码由上述哈夫曼编码组成的0101串
huffmanCode = "00100011000001";
source = huffmanCode2String(huffmanCode, hc);
System.out.println("\n===============【00100011000001】对应的字符串===================");
System.out.println(source);
huffmanCode = "00111001";
source = huffmanCode2String(huffmanCode, hc);
System.out.println("\n===============【00111001】对应的字符串===================");
System.out.println(source);
}
/**
* 功能:构造哈夫曼树
* 说明:该哈夫曼树使用顺序存储的方式存储在一个HuTNode类型的数组中,且该数组0号下标不使用
* @return 返回根据给定节点和权重构造的哈夫曼树
*/
private static HuTNode[] creatHuTree() {
// 输入控制
Scanner in = new Scanner(System.in);
System.out.print("请输入各叶子节点数据域的值,以空格分隔:");
String[] nodes = in.nextLine().split(" ");
System.out.print("请输入各叶子节点对应的权重:");
String[] weights = in.nextLine().split(" ");
// 初始化哈夫曼树
int n = nodes.length;// 带权叶子节点数
int m = 2*n;// 由于底层数组0号位置不存放节点,所以n个叶子节点合并生成哈夫曼树后,需要2n-1+1,即2n长度的数组
HuTNode[] ht = new HuTNode[m];
for (int i=0; i<n; ++i){
HuTNode newNode = new HuTNode(nodes[i].charAt(0), Integer.valueOf(weights[i]));
ht[i+1] = newNode;
}
// 选择权重最小的节点两两合并构造哈夫曼树
for (int i=0; i<n-1; ++i){// n个叶子节点需要合并n-1次
int[] arr = selectIndexOfMinWeight(ht);// 获取权重最小的两个节点的下标
// 创建合并的新节点
HuTNode newNode = new HuTNode();
newNode.lChild = arr[0];
newNode.rChild = arr[1];
newNode.weight = ht[arr[0]].weight + ht[arr[1]].weight;
ht[n+i+1] = newNode;// 此时底层数组中有效节点有n+i个,将新生成的节点保存在数组中时,下标应为n+i+1
// 修改被合并节点的双亲域
ht[arr[0]].parent = n+i+1;
ht[arr[1]].parent = n+i+1;
}
return ht;
}
/**
* 在所有节点双亲域的值是0节点中,选择两个权重最小的节点,并将他们在底层数组中的下标打包成数组进行返回
* @param ht 底层数组
* @return 最小权重节点的下标打包成的数组
*/
private static int[] selectIndexOfMinWeight(HuTNode[] ht) {
int[] arr = new int[2];
List<IndexAndWeight> list = new LinkedList<>();
int i = 1;
while (ht[i] != null){
if (ht[i].parent != 0){// 过滤掉存在双亲的节点
++i;
continue;
}
list.add( new IndexAndWeight(i, ht[i].weight) );
++i;
}
// 根据权重从从小到大排序
list.sort(new Comparator<IndexAndWeight>() {
@Override
public int compare(IndexAndWeight o1, IndexAndWeight o2) {
if (o1.weight > o2.weight){
return 1;
}else {
return -1;
}
}
});
arr[0] = list.get(0).index;
arr[1] = list.get(1).index;
return arr;
}
/**
* 根据哈夫曼树ht,将每个给定字符及其哈夫曼编码存储到一个HuCode类型数组中
* @param ht 哈夫曼树
* @return 字符和对应哈夫曼编码组成的数组,即哈夫曼编码集
*/
private static HuCode[] getHuCode(HuTNode[] ht) {
int n = (ht.length+1)/2;// 叶子节点个数
HuCode[] hc = new HuCode[n];
// 从每个叶子节点回溯找其双亲节点,并根据每一次回溯的路径,保存0或1,知道找到根节点为止
Stack<Character> stack = new Stack<>();// 用于保存回溯得到的0或1
for (int i=1; i<=n; ++i){
stack.clear();
// 回溯,并将0或1入栈
HuTNode child = ht[i];// 叶子节点
int childIndex = i;
while (child.parent != 0){
int parentIndex = child.parent;
HuTNode parent = ht[parentIndex];
if (parent.lChild == childIndex){// child是parent的左孩子,将0入栈
stack.push('0');
}else {// child是parent的右孩子,将1入栈
stack.push('1');
}
child = ht[child.parent];
childIndex = parentIndex;
}
// stack按出栈顺序组成对应字符的哈夫曼编码,
StringBuilder code = new StringBuilder("");
while (!stack.isEmpty()){
code.append(stack.pop());
}
// 保存在hc数组中
hc[i-1] = new HuCode(ht[i].data, code.toString());
}
return hc;
}
/**
* 根据给定的哈夫曼编码集,将指定字符串编码成0101串
* @param source 字符串
* @param hc 哈夫曼编码集
* @return 0101串
*/
private static String String2HuffmanCode(String source, HuCode[] hc) {
StringBuilder huffmanCode = new StringBuilder("");
for (int i=0; i<source.length(); ++i){
char c = source.charAt(i);
boolean flag = false;
for (HuCode huCode: hc){
if (huCode.data == c){
huffmanCode.append(huCode.code);
flag = true;// 表示编码成功
}
}
if (!flag){
throw new RuntimeException("【" + c + "】不存在对应的哈夫曼编码");
}
}
return huffmanCode.toString();
}
/**
* 根据给定的哈夫曼编码集,将指定0101串解码成字符串
* @param huffmanCode 0101串
* @param hc 哈夫曼编码集
* @return 字符串
*/
private static String huffmanCode2String(String huffmanCode, HuCode[] hc) {
StringBuilder source = new StringBuilder("");
StringBuilder tempCode = new StringBuilder("");
for (int i=0; i<huffmanCode.length(); ++i){
tempCode.append(huffmanCode.charAt(i));
// 判断当前tempCode是否在哈夫曼编码集中
for (HuCode huCode: hc){
if ( huCode.code.equals(tempCode.toString()) ){
tempCode.delete(0, tempCode.length());// 清空tempCode
source.append(huCode.data);
}
}
}
if (!tempCode.toString().equals("")){
throw new RuntimeException("【" + huffmanCode + "】解码失败!");
}
return source.toString();
}
//数组元素类(哈夫曼树节点类)
static class HuTNode{
char data;// 数据域
int weight;// 权重
int parent;// 双亲
int lChild;// 左孩子
int rChild;// 右孩子
public HuTNode(char data, int weight) {
this.data = data;
this.weight = weight;
}
public HuTNode() {
}
@Override
public String toString() {
return this.data + "\t\t" + this.weight + "\t\t" + this.parent + "\t\t" + this.lChild + "\t\t" + this.rChild;
}
}
// 用于合并节点时,筛选出权重最小两个节点用的内部类,封装了节点在底层数组中的下标,以及权重
static class IndexAndWeight{
int index;// 下标
int weight;// 权重
public IndexAndWeight(int index, int weight) {
this.index = index;
this.weight = weight;
}
}
// 编码类,封装了字符及对应的哈夫曼编码
static class HuCode{
char data;
String code;
public HuCode(char data, String code) {
this.data = data;
this.code = code;
}
@Override
public String toString() {
return data + "\t\t" + code;
}
}
}
三、测试