当前位置:   article > 正文

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
  • 2
  • 3
  • 4
  • 5

二、哈夫曼算法

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

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();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

2.创建哈夫曼树

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

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

代码如下(示例):

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
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

三、哈夫曼编码

1.定义

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

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

2.思路

1.统计各个字符出现的次数
2.以字符出现次数作为权值(键值对中的值),字符作为键值对的键构建哈夫曼树
3.将字符串转为变长编码
  • 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102

具体编程思路

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

2.构建哈夫曼编码表

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

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

代码如下(示例):

//用于拼接路径
    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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/693594
推荐阅读
相关标签
  

闽ICP备14008679号