《数据结构与算法之哈夫曼树(Java实现)》

说在前头: 本人为大二在读学生,书写文章的目的是为了对自己掌握的知识和技术进行一定的记录,同时乐于与大家一起分享,因本人资历尚浅,能力有限,文章难免存在一些错漏之处,还请阅读此文章的大牛们见谅与斧正。若在阅读时有任何的问题,也可通过评论提出,本人将根据自身能力对问题进行一定的解答。

前言

对于哈夫曼树的介绍,这里我们引用一下百度百科的介绍:
在这里插入图片描述
但百度百科上的介绍可能对想要了解哈夫曼树的初学者不太友好,解释的过于专业或"难懂",此篇文章将会以更加通俗易懂的方式讲解哈夫曼树,准备好,发车咯!!


哈夫曼编码


什么是压缩算法

哈夫曼编码是一种压缩算法,问题又来了,什么是压缩算法?我们通过举一个例子来解释这个问题:当我们需要对下面的字符串进行压缩时,你会使用怎样的方式?

AAAAAAAABBBBBBBCCCCCC

我们可以使用“数次数”的方式对这段字符串进行压缩,如下:(8个A,7个B,6个C)

8A7B6C

此时原本由21个字符组成的数据,我们使用6个字符组成就可表达,大大的减少了存储空间,当我们需要使用该数据时,根据压缩该字符串的规则对其进行解压即可。这就是最简单的压缩算法。

什么是哈夫曼编码

接下来我们将回到文章的主要话题“哈夫曼”中。上面讲述的最简单的算法,是根据字符连续出现的次数为关键进行压缩,同时缺点也很明显,当需要压缩的字符串中,不存在连续出现的字符时,该算法对数据的压缩效率将为零。而哈夫曼算法则是利用字符出现的频率为关键,对数据进行压缩。继续使用上面的字符串:

AAAAAAAABBBBBBBCCCCCC

如果对该字符串以ASCII的编码方式存储在计算机的硬盘中时,字符A对应的二进制码为0100 0001,B为0100 0010,C为0100 0011,因此,将该字符串存入计算机时,保存的信息将会如下

0100 0001 0100 0001 0100 0001 0100 0001 0100 0001 0100 0001 0100 0001 0100 0001
0100 0010 0100 0010 0100 0010 0100 0010 0100 0010 0100 0010 0100 0010
0100 0011 0100 0011 0100 0011 0100 0011 0100 0011 0100 0011

通过上面的二进制码可以发现,出现频率最高的A字符,占据了大部分的存储空间。而哈夫曼编码可以解决这个问题,出现频率越高的字符,我们使用越短的二进制码进行存储。如上面的字符串,出现频率最高的A,我们可以使用0表示,B使用1表示,出现频率最小的C使用10表示。这样经过哈夫曼编码的数据存入计算机时将会如下:

00000000 1111111 1010101010


哈夫曼树


解释完什么是哈夫曼编码后,接下来解释哈夫曼树就会变得简单很多了。可以这么形容哈夫曼树和哈夫曼编码的关系,`哈夫曼编码是一种思想,而具体实现这一思想的就是我们的哈夫曼树`,当然,这只是我自己的一种看法,若各位小伙伴有更好的说法还行不吝赐教!

哈夫曼树与我们之前讲解的二叉搜索树,红黑树等有所不同,这些树都是自上而下的创建树,即拥有了父节点后,往父节点下的子节点中插入数据。而哈夫曼树不同,哈夫曼树的创建,是由下而上的!

哈夫曼树的创建流程

需要存储的数据:{1,4,11,14,23,27,34,80}。存储规则,每次从该数组中取出两个权值最小的数据,并将这两个数据的权值相加,代替数组中的这两个数据,如此反复,直到数组中只剩下一个数。为了让大家更加清晰的了解哈夫曼树的创建过程,我将使用多张流程图进行演示,如下:
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

创建树后,数据对应的哈夫曼编码

哈夫曼树的数据编码信息,如下规则生成:从根节点开始计算,从上往下寻找节点,寻找左树时记为0,寻找右树时记为1。比如:寻找下图中的23时:从根节点194开始,需要访问左子树114,此时编码信息记为0,而后需要访问114的右子树,此时编码信息记为01,最后访问50的左子树,找到节点23,此时的编码信息为010,所以节点23的哈夫曼编码值为010
在这里插入图片描述
按照上述规则,其余的数据哈夫曼编码值都可以算出:1的编码信息为000000,4为000001,11为00001,14为0001,34为001,23为010,27为011,80为1

具体代码

节点类:Node.java

package com.bosen.www;

/**
 * <p>节点类:实现Comparable接口完成从小到大排序功能</p>
 * @author Bosen 2021/7/3 10:59
 */
public class Node implements Comparable<Node> {
    private Node left;  // 左子节点
    private int value;  // 权值

    private Node right; // 右子节点

    public Node(Node left, int value, Node right) {
        this.left = left;
        this.value = value;
        this.right = right;
    }

    @Override
    public int compareTo(Node o) {
        // 从小到大排序
        return this.value - o.value;
    }

    @Override
    public String toString() {
        return "Node{" +
                "value=" + value +
                ", left=" + left +
                ", right=" + right +
                '}';
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public Node getLeft() {
        return left;
    }

    public void setLeft(Node left) {
        this.left = left;
    }

    public Node getRight() {
        return right;
    }

    public void setRight(Node right) {
        this.right = right;
    }
}

哈夫曼树:Huffman.java

package com.bosen.www;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * <p>哈夫曼树</p>
 * @author Bosen 2021/7/3 10:59
 */
public class Huffman {
    /*
     * 根节点
     */
    public Node root;

    public void create(int[] array) {
        List<Node> nodeList = new ArrayList<>();
        for (int i : array) {
            Node node = new Node(null, i, null);
            nodeList.add(node);
        }
        while (nodeList.size() > 1) {
            // 从小到大进行排序
            Collections.sort(nodeList);
            // 取出权值最小的两个节点
            Node left = nodeList.get(0);
            Node right = nodeList.get(1);
            // 构建新的二叉树
            Node node = new Node(left, left.getValue()+right.getValue(), right);
            // 删除最小的两个节点
            nodeList.remove(left);
            nodeList.remove(right);
            // 将新的节点存入list中
            nodeList.add(node);
        }
        // 将list剩下的一个节点作为根节点+
        root = nodeList.get(0);
    }

    /*
     * 中序遍历
     */
    public void traverse(Node node) {
        if (node != null) {
            traverse(node.getLeft());
            System.out.print(node.getValue()+"\t");
            traverse(node.getRight());
        }
    }
}

测试类:Test.java

package com.bosen.www;

/**
 * <p>测试类</p>
 * @author Bosen 2021/7/3 10:59
 */
public class Test {
    public static void main(String[] args) {
        int[] array = {1, 4, 11, 14, 23, 27, 34, 80};
        Huffman huffman = new Huffman();
        huffman.create(array);
        huffman.traverse(huffman.root);
    }
}

测试结果
在这里插入图片描述

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云丶言

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值