其中包含两个案列,一个是特定的编码,还有一种是键盘输入自动计算权值以及解码。案例一分别使用了数组和链式栈实现哈夫曼树的编码同时可以计算压缩率,案例二使用先序遍历进行编码计算总码长
具体代码如下
主类
public class Testmian {//测试类
public static void main(String[] args) {//传入固定字符和频率
char[] ch={'a','b','c','d','e','f','g','h'};
double[] weight={0.03,0.06,0.09,0.12,0.15,0.18,0.21,0.16};
HFMTool hfm1=new HFMTool(ch,weight);
//设置需要编码的字符输入字符串进行解码
HFMTool hfm2=new HFMTool();
hfm2.create();//创建哈夫曼树并输出结构和所有的字符编码
String op="";
do{
System.out.println("请输入一个报文进行编码:");
Scanner sc = new Scanner(System.in);
String codes=sc.nextLine();
String decodes=hfm2.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"));
}
}
三中数据结构
哈弗曼树节点的设计
public class HFMTreeNode {//哈弗曼树节点的设计
double weight;
int parent,lchild,rchild;
String code,name;
HFMTreeNode(){
weight=0;
parent=lchild=rchild=-1;
}
}
哈夫曼编码节点的设计
public class HFMCodeNode {//哈夫曼编码节点的设计
int[] bit;//存储叶节点的编码0或1
int start;//标记bit数组为真正编码存储开始的位置。
HFMCodeNode(int n){//n为哈夫曼树的叶节点数目,节点数为n的哈夫曼树的长度最多为n-1个
bit=new int[n];
start=n-1;
}
}
链式栈的实现用于哈夫曼编码
public interface IStack<E> {//栈接口 用于实现链式栈
E push(E item); //入栈
E pop(); //出栈
E peek(); //取栈顶元素
int size(); //返回栈中元素的个数
boolean empty(); //判断栈是否为空
}
public class StackNode<E> {//定义对数据设置获取等基本操作,简化对对象的操作步骤,链表的结构所以从链头加入
private E data; // 数据域
private StackNode<E> next; // 引用域
//构造函数
public StackNode(){}
public StackNode(E data) {
this.data = data;
}
public StackNode(E data, StackNode<E> next) {
super();
this.data = data;
this.next = next;
}
//数据域get属性
public E getData() {
return data;
}
//数据域set属性
public void setData(E data) {
this.data = data;
}
//引用域get属性
public StackNode<E> getNext() {
return next;
}
//引用域get属性
public void setNext(StackNode<E> next) {
this.next = next;
}
}
public class LinkStack<E> implements IStack<E> {
private StackNode<E> top; // 栈顶指示器
private int size; // 栈中结点的个数
// 初始化链栈
public LinkStack() {
top = null;
size = 0;
}
// 入栈操作
public E push(E item) {
StackNode<E> newnode = new StackNode<E>(item);
if (!empty())
newnode.setNext(top);
top = newnode;
++size;
return item;
}
// 出栈操作
public E pop() {
E item=null;
if (!empty()){
item = top.getData();
top = top.getNext();
size--;
}
return item;
}
// 获取栈顶数据元素
public E peek() {
E item=null;
if (!empty()){
item=top.getData();
}
return item;
}
// 求栈的长度
public int size() {
return size;
}
// 判断顺序栈是否为空
public boolean empty() {
if ((top == null) && (size == 0)){
return true;
}else{
return false;
}
}
}
主方法类
public class HFMTool {//计算哈夫曼树实现哈弗曼编码
private int leaf;//树的结点的数量
private HFMTreeNode[] data;//数据存储结构
private int len;//报文大小
//构造方法
public HFMTool(){}
public HFMTool(String str){//读入字符串
}
//传入固定字符和频率
public HFMTool(char[] ch,double[] weight){
createHFMTree(ch,weight);
}
//计算哈夫曼树
public static void createHFMTree(char[] cn,double[] weight){
int n=cn.length;
//创建哈弗曼树数组
HFMTreeNode[] hfmtree=new HFMTreeNode[2*n-1];
//初始化哈弗曼树数组
for(int i=0;i<2*n-1;i++)
hfmtree[i]=new HFMTreeNode();
//给哈弗曼树节点权值
for(int i=0;i<n;i++) hfmtree[i].weight=weight[i];
double x1,x2;//记录每次选择的两个最小值,x1<x2
int m1,m2;//记录最小的位置
for(int i=0;i<n-1;i++){//构建新树的次数
//选择权值最小的两个结点
x1=x2=1;//为无穷大的值
m1=m2=0;
for(int j=0;j<n+i;j++){
if(hfmtree[j].parent==-1 && hfmtree[j].weight<=x1){
x2=x1;
m2=m1;
x1=hfmtree[j].weight;
m1=j;
}else if(hfmtree[j].parent==-1 && hfmtree[j].weight<=x2){
x2=hfmtree[j].weight;
m2=j;
}
}
//构建新结点
hfmtree[n+i].weight=x1+x2;
hfmtree[n+i].lchild=m1;
hfmtree[n+i].rchild=m2;
hfmtree[m1].parent=n+i;
hfmtree[m2].parent=n+i;
}
//输出哈弗曼树
pnthfmtree(hfmtree);
//创建哈弗曼编码输出HFM编码
//数组实现
createHFMCode1(hfmtree,cn);
//链式栈实现
createHFMCode2(hfmtree,cn,weight);
}
//计算哈夫曼编码
//数组实现
public static void createHFMCode1(HFMTreeNode[] hfmtree,char[] cd){
int n=cd.length;
//创建哈弗曼编码数组
HFMCodeNode[] hfmcd=new HFMCodeNode[n];
//初始化哈弗曼编码数组
for(int i=0;i<n;i++)hfmcd[i]=new HFMCodeNode(n);
for(int i=0;i<n;i++){//计算结点的次数 i表示树结点的位置
int c=i;//当前结点
int p=hfmtree[i].parent;//该结点的父结点
while(p!=-1){
if(hfmtree[p].lchild==c){
hfmcd[i].bit[hfmcd[i].start]=0;
}else if(hfmtree[p].rchild==c){
hfmcd[i].bit[hfmcd[i].start]=1;
}
hfmcd[i].start--;
c=p;//父结点为当前结点
p=hfmtree[c].parent;
}
}
pnthfmcode1(hfmcd,cd);
}
//链式栈实现
public static void createHFMCode2(HFMTreeNode[] hfmtree,char[] aa,double[] weight){
LinkStack stack;
System.out.println("链式栈实现霍夫曼编码为:");
int n=aa.length;
//用三位二进行数进行的等长编码平均长度为3的平均码长
double[] m=new double[n];
double corn=0;//平均码长是等长码的概率
double pas=0;//平均压缩率
Integer a=0,b=1;
for(int i=0;i<n;i++){stack = new LinkStack<Integer>();
int c=i;//当前结点
int p=hfmtree[i].parent;//该结点的父结点
int o=0;//统计编码数
int one=0,two=0,three=0;//用于标记编码的最高三位 当不足三位时补0
while(p!=-1){
if(hfmtree[p].lchild==c){
stack.push(a);o++;
if(o==1){
one=1;
}else if(o==2){
two=1;
}else if(o==3){
three=1;
}
}else if(hfmtree[p].rchild==c){
stack.push(b);o++;
}
c=p;//父结点为当前结点
p=hfmtree[c].parent;
}
m[i]=o;
pnthfmcode2(stack,aa[i],o);
}
//计算用三位二进行数进行的等长编码平均长度为3的平均码长
for(int g=0;g<n;g++)
corn+=m[g]*weight[g];
System.out.println("用三位二进行数进行的等长编码平均长度为3的平均码长:");
System.out.println("平均码长是等长码的:"+(int)(corn/3*100)+"%");
System.out.println("平均压缩率为: "+(int)(100-corn/3*100)+"%");
}
//输出哈夫曼数
public static void pnthfmtree(HFMTreeNode[] hfmtree){
System.out.println("霍夫曼树为:");
for(int i=0;i<hfmtree.length;i++){
System.out.printf("%d: %.2f %d %d %d\n",i,hfmtree[i].weight,hfmtree[i].lchild,hfmtree[i].rchild,hfmtree[i].parent);
}
}
//输出数组哈夫曼编码
public static void pnthfmcode1(HFMCodeNode[] hfmcd,char[] ch){
System.out.println("数组实现霍夫曼编码为:");
int n=ch.length;
//输出每个节点的哈夫曼编码值
for(int i=0;i<n;i++){
System.out.print(ch[i]+": ");
for(int j=hfmcd[i].start+1;j<n;j++)
System.out.print(hfmcd[i].bit[j]+" ");
System.out.println();
}
}
//输出链式栈哈夫曼编码
public static void pnthfmcode2(LinkStack sta,char bb,int o){
//输出每个节点的哈夫曼编码值
System.out.print(bb+": ");
for(int j=0;j<o;j++)
System.out.print(sta.pop()+" ");
System.out.println();
}
//设置需要编码的字符输入字符串进行解码
public boolean isLeaf(HFMTreeNode p){//判断是否是叶子结点
return ((p!=null) && (p.lchild==-1) && (p.rchild==-1));
}
//创建哈夫曼树
public void create(){
Scanner sc=new Scanner(System.in);
System.out.println("请输入需要传输的报文:");
String str = sc.nextLine().toLowerCase();
str=str.replaceAll(" ","");//去掉空格
len=str.length();
double[] c=new double[26]; //统计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++;
}
for(int i=0;i<str.length();i++){//统计字符出现频率
if(c[i]>0)
c[i]=c[i]/str.length();
}
this.leaf=cnt;
data = new HFMTreeNode[this.leaf*2-1];
for(int i=0;i<2*leaf-1;i++)
data[i]=new HFMTreeNode();
cnt=0;
char[] aa = new char[this.leaf];int o=0;//统计出现过的字符
for(int i=0;i<26;i++){//用字符创建叶子结点
if(c[i]>0){
data[cnt].name=(char)(i+'a')+"";
data[cnt++].weight=c[i];
aa[o++]=(char)(i+'a');
}
}
double x1,x2;//记录每次选择的两个最小值,x1<x2
int m1,m2;//记录最小的位置
for(int i=0;i<this.leaf-1;i++){//构建新树的次数
//选择权值最小的两个结点
x1=x2=1;//为无穷大的值
m1=m2=0;
for(int j=0;j<this.leaf+i;j++){
if(data[j].parent==-1 && data[j].weight<=x1){
x2=x1;
m2=m1;
x1=data[j].weight;
m1=j;
}else if(data[j].parent==-1 && data[j].weight<=x2){
x2=data[j].weight;
m2=j;
}
}
//构建新结点
data[this.leaf+i].weight=x1+x2;
data[this.leaf+i].lchild=m1;
data[this.leaf+i].rchild=m2;
data[m1].parent=this.leaf+i;
data[m2].parent=this.leaf+i;
}
//输出哈弗曼树
outTree(data);
//先序遍历计算叶子结点编码,层序遍历 求所有报文字符编码,计算报文传送总长度
traverse(data);
}
//输出哈夫曼树
private void outTree(HFMTreeNode[] data){
for(int i=0;i<2*leaf-1;i++){
System.out.printf("%d:%.2f %d %d %d\n",i,data[i].weight,
data[i].lchild,data[i].rchild,data[i].parent);
}
}
//先序遍历,输出所有叶子结点的编码,并计算报文总长度
private int preorder(HFMTreeNode root,String code){
int sum=0;
if(root!=null){
root.code=code;
if(isLeaf(root)){ //叶子结点输出编码 计算长度
System.out.println(root.name+":"+root.code);
return (int) (root.weight*len*root.code.length());
}
if(root.lchild!=-1){
sum+=preorder(data[root.lchild],code+"0");
}
if(root.lchild!=-1){
sum+=preorder(data[root.rchild],code+"1");
}
}
return sum;
}
//层序遍历 求所有报文字符编码,计算报文传送总长度 同时调动编码递归preorder函数
private void traverse(HFMTreeNode[] data){
int root=2*leaf-2;//根结点位置
if(root == -1){
return;
}
int sum=preorder(data[root],"");
System.out.println("所有报文长度:"+sum);
}
//采用层序遍历进行报文解密 --解码
public String decodes(String codes){
int root=2*leaf-2;//根结点位置
if(root==-1){//根结点为空
return "";
}
Queue<HFMTreeNode> q=new LinkedList<HFMTreeNode>();//设置一个队列保存层序遍历的结点
q.add(data[root]);//根结点入队
int i=0;
String str="";
while(!q.isEmpty()){//队列非空结点未处理完毕
HFMTreeNode tmp=q.poll();//节点出队
if(!codes.startsWith(tmp.code)) continue;
if(isLeaf(tmp)){//如果是叶子结点就计算编码长度
str=str+tmp.name;
codes=codes.substring(tmp.code.length());
if(codes.length()>0){//如果存在多个报文字符,则继续重新编码、
while(!q.isEmpty()) q.poll();
q.add(data[root]);
continue;
}
}
if(tmp.lchild!=-1){//当前结点左孩子入队
q.add(data[tmp.lchild]);
}
if(tmp.rchild!=-1){//当前结点右孩子入队
q.add(data[tmp.rchild]);
}
}
return str;
}
}