哈夫曼树的构造和代码实现

1.哈夫曼树的定义

树中的 节点被赋予一个表示某种意义的数值,称为该节点的。给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

2. 哈夫曼树的构造

1.将n个结点分别作为n棵只含一个节点的二叉树,构建成森林F
2.构造一个新结点,从F中选取两棵根结点权值最小的树作为左右子树,并且新节点为左右子树之和
3.从F中删除那两棵树,加入新结点进入F
4.重复2.3直到F中只留下一棵树

如下图为一棵哈夫曼树(数字表示各个数字的权重):
在这里插入图片描述
根据哈夫曼树可以得到哈夫曼编码(左子树为0,右子树为1):
a:000
b:001
c:01
d:10
e:11
哈夫曼编码是前缀编码,特点是任意一个字母的编码都不会是另外一个字母的编码的前缀

3.哈夫曼树的特点:

1.每个初始结点最终都成为叶结点,且权值越小的结点到根结点路径越大。
2.构建过程中共新建n-1个结点,因此哈夫曼树结点总数为2*n-1.
3.哈夫曼树不存在度为1的结点。

4.代码实现

public class HfmTree {
    //根结点
    HfmNode root;
    //叶子结点的权重表
    Map<Character, Integer> weights;
    //叶子结点
    List<HfmNode> leafs;
    //计算出的每个叶子结点的编码表
    Map<Character, String> strCode;

    public HfmTree(Map<Character, Integer> weights) {
        this.weights = weights;
        leafs = new ArrayList<>();
        strCode = new HashMap<>();
        createTree();
        code();
    }

    /**
     * 创建哈夫曼树
     */
    private void createTree() {
        // 拿出所有的点
        Character[] keys = weights.keySet().toArray(new Character[0]);
        // jdk底层的优先队列
        PriorityQueue<HfmNode> queue = new PriorityQueue<>();
        for (Character c : keys) {
            HfmNode node = new HfmNode();
            node.chars = c.toString();
            // 权重
            node.fre = weights.get(c);
            //记录下所有初始节点,他们都会是叶子结点
            leafs.add(node);
            // 把我们的优先队列初始化进去
            queue.add(node);
        }
        int len = queue.size();
        // 每次找最小的两个点合并
        for (int i = 0; i < len - 1; i++) {
            // 每次取优先队列的前面两个 就一定是两个最小的
            HfmNode n1 = queue.poll();
            HfmNode n2 = queue.poll();
            HfmNode newNode = new HfmNode();
            // 我们把值赋值一下,也可以不复制
            newNode.chars = n1.chars + n2.chars;
            // 把权重相加
            newNode.fre = n1.fre + n2.fre;

            // 维护出树的结构
            newNode.left = n1;
            newNode.right = n2;
            n1.parent = newNode;
            n2.parent = newNode;

            queue.add(newNode);
        }
        // 最后这个点就是我们的根节点
        root = queue.poll();
    }

    /**
     * 构造哈夫曼编码表
     */
    private void code() {
        for (int i = 0; i < leafs.size(); i++) {
            HfmNode node = leafs.get(i);
            // 叶子节点肯定只有一个字符
            Character c = node.chars.charAt(0);
            String code = "";
            //parent==null说明到了根结点
            while (node.parent != null) {
                //当前结点是左结点为0,右节点为1
                code = node == node.parent.left ? "0" + code : "1" + code;
                node = node.parent;
            }
            strCode.put(c, code);
            System.out.println(c + ":" + code);
        }
    }

    /**
     * 编码
     */
    public String encode(String str) {
        char[] chars = str.toCharArray();
        String encode = "";
        for (int i = 0; i < chars.length; i++) {
            encode = encode + strCode.get(chars[i]);
        }
        return encode;
    }

    /**
     * 解码
     */
    public String decode(String str) {
        char[] chars = str.toCharArray();
        HfmNode curr = root;
        String decode = "";
        for (int i = 0; i < chars.length; i++) {
            curr = chars[i] == '0' ? curr.left : curr.right;
            if (curr.left == null && curr.right == null) {
                decode = decode + curr.chars;
                curr = root;
            }
        }
        return decode;
    }

    /**
     * 树的结点
     */
    class HfmNode implements Comparable<HfmNode> {
        //节点里面的字符
        String chars;
        //权重
        int fre;
        //用来找上层的
        HfmNode parent;
        HfmNode left;
        HfmNode right;

        @Override
        public int compareTo(HfmNode o) {
            return this.fre - o.fre;
        }

    }

    /**
     * 测试
     */
    public static void main(String[] args) {
        // a:3 b:4 c:8 d:12 e:13
        Map<Character, Integer> weights = new HashMap<Character, Integer>();
        weights.put('a', 3);
        weights.put('b', 4);
        weights.put('c', 8);
        weights.put('d', 12);
        weights.put('e', 13);


        HfmTree huffmenTree = new HfmTree(weights);

        String str = "abcde";
        String str1 = huffmenTree.encode(str);
        String str2 = huffmenTree.decode(str1);
        System.out.println("编码前"+str);
        System.out.println("编码后的:" + str1);
        System.out.println("解码后" + str2);
    }
}

测试结果如下:

a:000
b:001
c:01
d:10
e:11
编码前abcde
编码后的:000001011011
解码后abcde

结果和上图相符合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值