哈夫曼树的创建与哈夫曼编码的实现
目的和要求:
(1)正确定义哈夫曼树结点
(2)掌握哈夫曼树的创建方法
(3)掌握根据哈夫曼树进行编码的方法
(4)根据哈夫曼编码解决实际问题
实验原理及内容:
(1)定义哈夫曼树结点
(2)哈夫曼树的创建方法
(3)根据哈夫曼树进行编码
实验步骤:
(1)定义哈夫曼树结点
(2)哈夫曼树的创建方法
(3)根据哈夫曼树进行编码
实验过程:
(1)哈夫曼树结点定义
public class HNode {
private int weight; //结点权值
private int lchild; //左孩子结点
private int rchild; //右孩子结点
private int parent; //父结点
private String name; //结点数据,存放字符名称
private String code;//存放叶子结点的字符编码
//构造器
public HNode(String name, int w){
this.weight = w;
this.name = name;
this.lchild = -1;
this.rchild = -1;
this.parent = -1;
this.code = "";
}
public HNode(){
this(null,0);
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public int getLchild() {
return lchild;
}
public void setLchild(int lchild) {
this.lchild = lchild;
}
public int getRchild() {
return rchild;
}
public void setRchild(int rchild) {
this.rchild = rchild;
}
public int getParent() {
return parent;
}
public void setParent(int parent) {
this.parent = parent;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
(2)哈夫曼树的定义、创建及哈夫曼编码、解码
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
public class HuffmanTree {
private HNode[] data; // 结点数组
private int leafNum; // 叶子结点数目
// 构造哈夫曼树
public void create() {
Scanner sc = new Scanner(System.in);
System.out.println("请输入要传输的报文:");
String str = sc.nextLine().toLowerCase();
str = str.replace(" ", ""); //去掉空格
int[] c = new int[26];
for (int i = 0; i < str.length(); i++) { // 统计各字符出现的频率
c[str.charAt(i) - 'a']++;
}
int cnt = 0;
for (int i = 0; i < 26; i++) { // 统计报文中字符的数量
if (c[i] > 0)
cnt++;
}
this.leafNum = cnt;
data = new HNode[this.leafNum * 2 - 1];
for (int i = 0; i < 2 * leafNum - 1; i++)
data[i] = new HNode();
cnt = 0;
for (int i = 0; i < 26; i++) { // 用字符创建叶子结点
if (c[i] > 0) {
data[cnt].setName((char) (i + 'a') + "");
data[cnt++].setWeight(c[i]);
}
}
int m1, m2, x1, x2;
// 处理n个叶子结点,建立哈夫曼树
for (int i = 0; i < this.leafNum - 1; ++i) {
m1 = m2 = Integer.MAX_VALUE; // m1:最小权值,m2:次小权值
x1 = x2 = 0; // x1:权值最小位置,x2:权值次小位置
// 在全部结点中找权值最小的两个结点
for (int j = 0; j < this.leafNum + i; ++j) {
if ((data[j].getWeight() < m1) && (data[j].getParent() == -1)) {
m2 = m1;
x2 = x1;
m1 = data[j].getWeight();
x1 = j;
} else if ((data[j].getWeight() < m2)
&& (data[j].getParent() == -1)) {
m2 = data[j].getWeight();
x2 = j;
}
}
// 用两个权值最小点构造一个新的中间结点
data[this.leafNum + i].setWeight(data[x1].getWeight()
+ data[x2].getWeight());
data[this.leafNum + i].setLchild(x1);
data[this.leafNum + i].setRchild(x2);
// 修改权值最小的两个结点的父结点指向
data[x1].setParent(this.leafNum + i);
data[x2].setParent(this.leafNum + i);
}
}
//输出哈夫曼树结构
public void print() {
System.out.println("位置\t字符\t权值\t父结点\t左孩子结点\t右孩子结点");
for (int i = 0; i < 2 * leafNum - 1; i++) {
System.out.printf("%d\t%s\t%d\t%d\t%d\t%d\r\n", i,
data[i].getName(), data[i].getWeight(),
data[i].getParent(), data[i].getLchild(),
data[i].getRchild());
}
}
// 前序遍历,输出所有叶子结点的编码,并计算总的报文编码长度
private int preorder(HNode root,String code) {
int sum = 0;
if (root != null) {
root.setCode(code);
if(isLeaf(root)){ //叶子结点,输出编码,并计算长度
System.out.println(root.getName() + ":" + root.getCode());
return root.getWeight()*root.getCode().length();
}
if(root.getLchild()!=-1){
//左子树,编码为0,并统计左子树叶子结点的编码长度
sum +=preorder(data[root.getLchild()],code+"0");
}
if(root.getRchild()!=-1){
//右子树,编码为1,并统计右子树所有叶子结点的编码长度
sum +=preorder(data[root.getRchild()],code+"1");
}
}
return sum;
}
//层序遍历,求报文传输的总长度
private void levelOrder(){
//根结点的位置
int root = 2*leafNum-2;
// 根结点为空
if (root == -1) {
return;
}
// 设置一个队列保存层序遍历的结点
Queue<HNode> q = new LinkedList<HNode>();
// 根结点入队
q.add(data[root]);
int sum = 0;
String code = "";
// 队列非空,结点没有处理完
while (!q.isEmpty()) {
// 结点出队
HNode tmp = q.poll();
code = tmp.getCode();
// 如果是叶子结点,则计算编码长度
if(isLeaf(tmp)){
sum +=tmp.getWeight()*tmp.getCode().length();
}
// 将当前结点的左孩子结点入队
if (tmp.getLchild() != -1) {
q.add(data[tmp.getLchild()]);
data[tmp.getLchild()].setCode(code+"0");
}
if (tmp.getRchild() != -1) {
// 将当前结点的右孩子结点入队
q.add(data[tmp.getRchild()]);
data[tmp.getRchild()].setCode(code+"1");
}
}
System.out.println("总的报文长度为:"+sum);
}
//采用层序遍历,进行报文解码
public String decodes(String codes){
//根结点的位置
int root = 2*leafNum-2;
// 根结点为空
if (root == -1) {
return "";
}
// 设置一个队列保存层序遍历的结点
Queue<HNode> q = new LinkedList<HNode>();
// 根结点入队
q.add(data[root]);
int i = 0;
String str = "";
// 队列非空,结点没有处理完
while (!q.isEmpty()) {
// 结点出队
HNode tmp = q.poll();
if(!codes.startsWith(tmp.getCode())) continue;
// 如果是叶子结点,则计算编码长度
if(isLeaf(tmp)){
str = str + tmp.getName();
codes = codes.substring(tmp.getCode().length());
if(codes.length()>0){ //如果存在多个报文字符,则继续重新解码
while(!q.isEmpty()) q.poll();
q.add(data[root]);
continue;
}
}
// 将当前结点的左孩子结点入队
if (tmp.getLchild() != -1) {
q.add(data[tmp.getLchild()]);
}
if (tmp.getRchild() != -1) {
// 将当前结点的右孩子结点入队
q.add(data[tmp.getRchild()]);
}
}
return str;
}
// 层次遍历
public void traverse() {
//根结点的位置
int root = 2*leafNum-2;
// 根结点为空
if (root == -1) {
return;
}
int sum = preorder(data[root],"");
System.out.println("所有报文长度为(位):"+sum);
}
// 判断是否是叶子结点
public boolean isLeaf(HNode p) {
return ((p != null) && (p.getLchild() == -1) && (p.getRchild() == -1));
}
}
(3)哈夫曼树测试
import java.util.Scanner;
public class TestHuffmanTree {
// 测试哈夫曼树
public static void main(String[] args) {
HuffmanTree ht = new HuffmanTree();
ht.create(); //创建哈夫曼树
//ht.print(); //输出哈夫曼树结构
ht.traverse(); //输出所有字符编码
String op = "";
do{
System.out.println("请输入一个报文编码进行解码:");
Scanner sc = new Scanner(System.in);
String codes = sc.nextLine();
String decodes = ht.decodes(codes); //报文解码
if(decodes.length()==0){
System.out.println("解码出错!");
}else{
System.out.println("对应的报文为:"+decodes);
}
System.out.println("按X键退出,其他键继续");
op = sc.nextLine();
}while(!op.toLowerCase().equals("x"));
System.out.println("程序退出");
}
}