Java数据结构之哈夫曼树与哈夫曼编码思路讲解


前言

在这里插入图片描述
上面这位就是本文的编码方式发明者, 哈夫曼先生

下面会介绍哈夫曼树的定义,名词解释(基本概念),构造算法和哈夫曼编码.


提示:以下是本篇文章正文内容,下面案例可供参考

一、Huffman Tree是什么?

1.定义

哈夫曼树是一种带权路径长度最短的树 , 下面会给出何为带权路径. 我们又称其为最优树 , 是目前效率最高的判别树.

我们在构造的过程中, 会人为地将权值大的结点放到较上层,如此, 该结点的路径长度最小,权值大,所以权值*路径长度 也就最小 (贪心)

2.基本概念

这里先给出一棵哈夫曼树,下面会根据此树解释概念

在这里插入图片描述

1. 路径长度: 分支上的所有路径数, 比如b的路径长为2,c为3
2. 权: 是对于实体的某一属性的量化描述 ,如下面会讲的字符出现次数
3. 树的带权路径长度: 树的每个叶子结点的带权路径长度之和(即 路径长度*权值,再求和),记作WPL,
	上图WPL=1*18+2*12+3*10+3*11
4. 哈夫曼树 : 即WPL最小的树

二、哈夫曼算法

下面说说如何 “拥有” 一颗哈夫曼树

1.结点是必需的

这里我们将结点的值域data同时也作为权值使用 , 由于哈夫曼树权重大的结点排列在顶层的原因,我们的Node类需要进行排序 , 因此需要实现一个Comparable接口用于规定排序规则

代码如下(示例):

class Node implements Comparable<Node>{
    int data ;
    Node leftNode;
    Node rightNode;
    public Node(int data) {
        this.data = data;
    }
    @Override
    public String toString() {
        return "Node{" +
                "data=" + data +
                '}';
    }
    @Override
    public int compareTo(Node o) {
        return this.data - o.data;
    }
    //先序遍历
    public void preOrder(){
        System.out.print(this.data+" ");
        if(this.leftNode != null){
            this.leftNode.preOrder();
        }
        if(this.rightNode != null){
            this.rightNode.preOrder();
        }
    }
}

2.创建哈夫曼树

关键点来了,先列举出思路

1. 定义一个辅助数组,并向其传入结点node
2.  循环,对目标数组(即传入的参数)进行排序,并取出每次排序结果中最小的两个结点
3. 创建一个新结点,将取出的两个树挂在新结点的同时,将新结点的权值置为二者权值之和(注意,不是值域)
4. 将新结点加入到辅助数组中并移出最小的那两个结点
5. 当数组的大小等于1时,循环终止	

代码如下(示例):

class HuffmanTree {
        public static Node createHuffmanTree(int[] arr){
        ArrayList<Node> nodes = new ArrayList<>();
        for (int val: //向array list传值
             arr) {
            nodes.add(new Node(val));
        }
        while(nodes.size() > 1){
            Collections.sort(nodes); //将nodes排序,排序得放里面排,因为remove后需重新排一次序
            //1. 找到权值最小的两个结点
            Node leftNode = nodes.get(0);
            Node rightNode = nodes.get(1);
            //2. 构建一颗新树
            Node parent = new Node(leftNode.data+rightNode.data);
            parent.leftNode = leftNode;
            parent.rightNode = rightNode;
            //3. 将新树加入到ArrayList
            nodes.add(parent);
            //4.删除结点
            nodes.remove(leftNode);
            nodes.remove(rightNode);
        }
        return nodes.get(0);
    }
}

三、哈夫曼编码

1.定义

哈夫曼树可以应用于数据的压缩 , 基本思想是对出现次数多的字符编较短的码,且是一种最优前缀编码 , 也就是说每个字符的编码都不会是其它字符编码的前缀.

在设计算法的时候我们可以将字符出现的次数作为权值,将出现次数最多的字符放到树的上层,就达到了压缩的效果

2.思路

1.统计各个字符出现的次数
2.以字符出现次数作为权值(键值对中的值),字符作为键值对的键构建哈夫曼树
3.将字符串转为变长编码

代码如下(示例):

public class HuffmanCodeDemo {

    public static void main(String[] args) {
        String str = "i like like like java do you like a java";
        byte[] strBytes = str.getBytes();
        System.out.println(strBytes.length);

        List<Node> nodes = HuffmanCode.getNodes(strBytes);
        Node root = HuffmanCode.createHuffmanCode(nodes);

        root.preOrder();

    }

    public static void preOrder(Node root){
        if(root != null){
            root.preOrder();
        }
    }
}

class HuffmanCode{

    public static List<Node> getNodes(byte[] bytes){
        //定义一个ArrayList,存放Node数组
        ArrayList<Node> nodes = new ArrayList<>();

        //统计各个字符出现的次数
        Map<Byte,Integer> counts = new HashMap<>(); //统计的map
        for (byte b:
             bytes) {
            Integer count = counts.get(b); //在map中查找b
            if(count == null){ //counts中还没加入b这个字符,则加入b,并且让b的出现次数(即权重为1)
                counts.put(b,1);
            } else {
                counts.put(b,++count);
            }
        }

        //把键值对转换成Node对象,并加入List<Node>
        for (Map.Entry<Byte,Integer> entry:
             counts.entrySet()) {
            nodes.add(new Node(entry.getKey(),entry.getValue()));
        }
        return nodes;
    }

    public static Node createHuffmanCode(List<Node> nodes){
        while(nodes.size() > 1){
            //先排序
            Collections.sort(nodes);
            //获取最小的两个结点
            Node leftNode = nodes.get(0);
            Node rightNode = nodes.get(1);

            //拼接成一颗新树
            //新树根节点没有值域,只有权值
            Node parent = new Node(null,leftNode.weight+rightNode.weight);
            parent.leftNode = leftNode;
            parent.rightNode = rightNode;
            //将新树加入到nodes
            nodes.add(parent);
            nodes.remove(leftNode);
            nodes.remove(rightNode);
        }
        return nodes.get(0) ; //返回根结点
    }
}

class Node implements Comparable<Node>{

    Byte data; //数据域
    Integer weight; //权重
    Node leftNode;
    Node rightNode;

    public Node(Byte data, Integer weight) {
        this.data = data;
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "Node{" +
                "data=" + data +
                ", weight=" + weight +
                '}';
    }
    @Override
    public int compareTo(Node o) {
        return this.weight - o.weight; //按照权值从小到大排列
    }
    public void preOrder(){
        System.out.println(this);
        if(this.leftNode != null){
            this.leftNode.preOrder();
        }
        if(this.rightNode != null){
            this.rightNode.preOrder();
        }
    }
}

具体编程思路

1. 最核心的部分是统计字符出现次数并将键值对转换为Node对象

2.构建哈夫曼编码表

思路有点难理解,需要debug慢慢调试哦~

1. 创建一个静态的StringBuilder和Map,前者用于拼接路径,后者用于构造编码表
2. 创建编码函数, 当前结点不为叶子结点时,对其左右子结点递归,
	反之以键值对方式加入编码表
3.重载该方法. 优雅地传入一个哈夫曼树的根节点,返回编码表	

代码如下(示例):

//用于拼接路径
    static StringBuilder stringBuilder = new StringBuilder();
    //用于存放哈夫曼编码
    static Map<Byte,String> huffmanCode = new HashMap<Byte, String>();

    //对getCodes(p1,p2,p3)重载
    public static Map<Byte,String> getCodes(Node root){
        if(root == null){
            return null;
        }
        getCodes(root.leftNode,"0",stringBuilder);
        getCodes(root.rightNode,"1",stringBuilder);
        return huffmanCode;
    }

    /**
     * 传入结点,转为Huffman编码
     * @param node 传入的结点
     * @param code 存放路径
     * @param stringBuilder 拼接
     *
     */
    public static void getCodes(Node node,String code,StringBuilder stringBuilder){
        //用传入的StringBuilder再生成一个StringBuilder
        StringBuilder stringBuilder1 = new StringBuilder(stringBuilder);
        stringBuilder1.append(code); //拼接code
        if(node != null){
            if(node.data == null){ //非叶子结点,就继续递归调用
                //递归调用左右结点
                getCodes(node.leftNode,"0",stringBuilder1);
                getCodes(node.rightNode,"1",stringBuilder1);
            } else { //叶子结点
                huffmanCode.put(node.data,stringBuilder1.toString()); //以键值对方式加入编码表
            }
        }

    }
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值