基于数组实现的Huffman树和Huffman编码

一、Huffman树简介

1、定义

树的带权路径长度,就是树中所有的叶节点的权值乘上其到根节点的路径长度。

        在含有n 个带权叶子结点的二叉树中,其中带权路径长度(Weighted Path Length, WPL)最小的二叉树称为哈夫曼树, 也称最优二叉树。如图,c树的WPL=35最小,经验证其为哈夫曼树。

c树的WPL = 7*1 + 5*2 + 2*3 + 4*3 = 35

2、特点

假设有n个带权的节点

1)每个初始结点最终都成为叶结点,也就是这n个节点全都初始化为root节点。

2)构造过程中共新建了n-1个结点 (双分支结点),因此哈夫曼树的结点总数为2n- 1 。

3)每次构造都选择 2 棵树作为新结点的孩子,因此哈夫曼树中不存在度为1 的结点。

举例:数组的长度已定,为2n - 1

二、算法实现思路和代码

实现思路

以 a 7 b 5 c 2 d 4为例:

1、定义节点类:

2、构建huffman树

1、先初始化n个节点:他们的parent、lchild和rchild全为-1,且它们在数组中的索引分别为0、1、2、3

2、将这n个节点两个两个的合并:每次选两个权值weight最小且parent等于-1的节点(要写一个selectMin方法获取最小的节点)

第一步:选两个权值weight最小且parent等于-1的节点

第二步:合并他们,则父节点的权重等于这两个子节点的权重的个,父节点的左右孩子分别为这两个子节点的索引,父节点的data值设置为null,对应代码:

ht[i] = new Node<>(null,ht[s1].weight + ht[s2].weight,-1,s1,s2);

第三步:设置子节点的parent为父节点在数组中的索引,对应代码:

ht[s1].parent = ht[s2].parent = i;

重复该步骤

节点上面为数组的下标

第一次:

第二次:

第三次:

构建完成。

3、完整代码

Node类:

public class Node<T>{
    T data;//存放字符
    double weight;//权重
    int parent;//父节点
    int lchild;//左孩子
    int rchild;//右孩子

    public Node(T data, double weight, int parent, int lchild, int rchild) {
        this.data = data;
        this.weight = weight;
        this.parent = parent;
        this.lchild = lchild;
        this.rchild = rchild;
    }
}

HuffmanTree类:

public class HuffmanTree <T>{
    Node<T>[] ht;//n个元素对应的节点的个数,2*n + 1
    String[] hc;//对应的字符编码
    int n;//n个权值

    public HuffmanTree(int n) {
        this.ht = new Node[2*n - 1];
        this.hc = new String[n];
        this.n = n;
    }


    //在0-  m-1中选择权值最小的节点
    public int selectMin(int m){
        int k = 0,i;
        //第一个parent不等于-1的(根节点)
        for (i = 0;i < m; i++) {
            if (ht[i].parent == -1){//找到根
                k = i;
                break;
            }
        }
        for (i += 1;i < m; i++) {
            if (ht[i].parent == -1 && ht[i].weight < ht[k].weight){
                k = i;
            }
        }
        ht[k].parent = -2;
        return k;
    }
    
    
    public void createTree(){
        int i,s1,s2;
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入" + this.n + "个元素的权值,用空格分割:");
        //创建n个叶子节点
        for (i = 0; i < n; i++) {
            T d = (T)scanner.next();//数据域
            double w = scanner.nextDouble();//权值
            ht[i] = new Node<>(d, w, -1, -1, -1);
        }
        //构造huffman树
        for (;i < this.ht.length; i++) {
            //选择权值最小的两个节点,这两个节点的parent为-1
            s1 = selectMin(i);
            s2 = selectMin(i);
            ht[s1].parent = ht[s2].parent = i;
            ht[i] = new Node<>(null,ht[s1].weight + ht[s2].weight,-1,s1,s2);
        }
    }


    public void displayTree(){
        for (int i = 0; i < ht.length; i++) {
            System.out.println("元素:" + ht[i].data + ",权值:" + ht[i].weight + ",父元素:" + ht[i].parent + ",左孩子:" + ht[i].lchild + ",右孩子:" + ht[i].rchild);
        }
    }

}

三、Huffman编码

对于一颗Huffman树,向左为0,向右为1

每个编码的长度不会超过n-1,char[] cd = new char[n - 1];

public String(char[] value,int offset,int count):分配一个新的 String,它包含取自字符数组参数一个子数组的字符。offset  参数是子数组第一个字符的索引,count 参数指定子数组的长度。该子数组的内容已被复制;后续对字符数组的修改不会影响新创建的字符串。

过程:

代码:

 public void createCode(){
        for (int i = 0; i < n; i++) {
            //按顺序生成元素的code
            char[] cd = new char[n - 1];
            int start = n - 2;//从后往前填充cd数组
            //从最下一层开始向上遍历,直到根节点,现在就根节点的parent=-1,其他的都不是-1
            for (int c = i, f = ht[c].parent; f != -1; c = f, f = ht[c].parent) {
                //找到当前节点是父节点的左孩子还是右孩子
                if (ht[f].lchild == c) {//左孩子
                    cd[start--] = '0';
                } else {//右孩子
                    cd[start--] = '1';
                }
            }
            hc[i] = new String(cd,start + 1,n - start - 2);
        }
    }

四、对电文进行解码和编码

1、编码

直接和ht数组的data比较,相同的话就拼接hc对应的code

 //将要发送的电文进行编码后返回
    public String textCode(String text){
        String codeStr = "";
        for (int i = 0; i < text.length(); i++) {
            //转换成String进行比较
            String s = String.valueOf(text.charAt(i));
            for (int j = 0; j < hc.length; j++) {
                if (ht[j].data.equals(s)){
                    codeStr += hc[j];
                    break;
                }
            }
        }
        return codeStr;
    }

2、解码

 //解码的操作
    public String textDecode(String text){
        String decodeStr = "";
        for (int i = 0; i < text.length();  i++) {
            int p = this.ht.length - 1;//从根开始进行遍历
            //遍历到叶子节点,叶子节点的lchild和rchild都是-1
            while(ht[p].lchild != -1 && ht[p].rchild != -1){
                char ch = text.charAt(i);//可能会数组越界
                if (ch == '0'){
                    p = ht[p].lchild;
                }else{
                    p = ht[p].rchild;
                }
                i++;
            }
            decodeStr += ht[p].data;
            i--;//循环结束后,i已经加1了,所以要减1,回退到上一个字符,不然会数组越界
        }
        return decodeStr;
    }

五、主类测试输出

public class Main {
    public static void main(String[] args) {
        System.out.println("请输入元素的个数:");
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        HuffmanTree<Character> huffmanTree = new HuffmanTree<>(n);
        huffmanTree.createTree();
        huffmanTree.displayTree();
        huffmanTree.createCode();
        huffmanTree.displayCode();
        String afterdataearareartarea = huffmanTree.textCode("abcd");
        System.out.println(afterdataearareartarea);
        String s = huffmanTree.textDecode(afterdataearareartarea);
        System.out.println(s);
    }
}

输出结果:

请输入元素的个数:
4
请输入4个元素的权值,用空格分割:
 a 7 b 5 c 2 d 4
元素:a,权值:7.0,父元素:6,左孩子:-1,右孩子:-1
元素:b,权值:5.0,父元素:5,左孩子:-1,右孩子:-1
元素:c,权值:2.0,父元素:4,左孩子:-1,右孩子:-1
元素:d,权值:4.0,父元素:4,左孩子:-1,右孩子:-1
元素:null,权值:6.0,父元素:5,左孩子:2,右孩子:3
元素:null,权值:11.0,父元素:6,左孩子:1,右孩子:4
元素:null,权值:18.0,父元素:-1,左孩子:0,右孩子:5
a元素的编码是:0
b元素的编码是:10
c元素的编码是:110
d元素的编码是:111
010110111
abcd
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值