哈夫曼树(Huffman Tree)及哈夫曼编码(Huffman Coding)

目录

一、Huffman树(最优二叉树)

1、定义

2、构造

构造哈夫曼树的算法

哈夫曼树特点

二、Huffman编码


一、Huffman树(最优二叉树)

1、定义

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

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

2、构造

构造哈夫曼树的算法

(给定n 个权值分别为wi的结点)

1)将这n个结点分别作为n棵仅含一个结点的二叉树,构成森林F。

2)构造一个新结点,从 F 中选取两棵根结点权值最小的树作为新结点的左、右子树,并且将新结点的权值置为左、右子树上根结点的权值之和。

3)从 F 中删除刚才选出的两棵树,同时将新得到的树加入F中。

4)重复步骤2)和 3), 直至F中只剩下一棵树为止。

//哈夫曼
class Node implements Comparable<Node> {
    Byte data;
    int weight;
    Node left;
    Node right;

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

    @Override
    public int compareTo(Node o) {
        return this.weight - o.weight;
    }

    @Override
    public String toString() {
        return "Node  [ data = " + data + "weight = " + weight + " ]";
    }

    //遍历
    public void preOrder() {
        System.out.println(this);
        if (this.left != null) {
            this.left.preOrder();
        }
        if (this.right != null) {
            this.right.preOrder();
        }
    }
}
private static Node createHuffmanTree(List<Node> nodes) {
    /**
         *@MethodName createHuffmanTree
         *@Description TODO 创建哈夫曼树
         *@Author SSRS
         *@Date 2020-10-31 13:21
         *@Param [nodes]
         *@ReturnType Java_note.Algorithm.HuffmanCode.Node
         */
    while (nodes.size() > 1) {
        //排序
        Collections.sort(nodes);
        //取最小两个二叉树
        Node leftNode = nodes.get(0);
        Node rightNode = nodes.get(1);
        //创建新的二叉树(他的根节点没有data,只有权值)
        Node parent = new Node(null, leftNode.weight + rightNode.weight);
        parent.left = leftNode;
        parent.right = rightNode;
        //删除已用结点
        nodes.remove(leftNode);
        nodes.remove(rightNode);
        //加入新节点
        nodes.add(parent);
    }
    return nodes.get(0);
}

哈夫曼树特点

1)每个初始结点最终都成为叶结点,且权值越小的结点到根结点的路径长度越大

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

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

例如,权值{7, 5, 2, 4}的哈夫曼树的构造过程如图所示。

二、Huffman编码

        在数据通信中,若对每个字符用相等长度的二进制位表示,称这种编码方式为固定长度编码。 若允许对不同字符用不等长的二进制位表示,则这种编码方式称为可变长度编码

        可变长度编码 比固定长度编码要好得多,其特点是对频率高的字符赋以短编码,而对频率较低的字符则赋以较长一些的编码,从而可以使字符的平均编码长度减短,起到压缩数据的效果。哈夫曼编码是一种被广泛应用而且非常有效的数据压缩编码

        若没有一个编码是另一个编码的前缀,则称这样的编码为前缀编码。哈夫曼编码是前缀编码。

 

class HuffmanCode {
    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(str + "的原长度是" + strBytes.length);
        HuffmanCode huffmanCode = new HuffmanCode();
        System.out.println("转成哈夫曼编码为:");
        String huffmanstr = huffmanCode.createHuffmanCode(strBytes);
        System.out.println(huffmanstr);
        System.out.println("其长度为:" + huffmanstr.getBytes().length);
        byte[] bytes = huffmanCode.zipbytes(strBytes);
        System.out.println("压缩后的结果是:" + Arrays.toString(bytes));
        System.out.println("其长度为:" + bytes.length);
        System.out.println("解压缩:" + new String(huffmanCode.rezip(bytes)));

    }

    private static List<Node> getNodes(byte[] bytes) {
        /**
         *@MethodName getNodes
         *@Description TODO 获得构建哈夫曼树的Node
         *@Author SSRS
         *@Date 2020-10-31 13:07
         *@Param [bytes]
         *@ReturnType java.util.List<Java_note.Algorithm.HuffmanCode.Node>
         */
        ArrayList<Node> nodes = new ArrayList<Node>();//要返回的nodes集合
        Map<Byte, Integer> counts = new HashMap<>();
        //统计每一个byte出现的次数
        for (byte b : bytes) {
            Integer count = counts.get(b);
            if (count == null) {
                counts.put(b, 1);
            } else {
                counts.put(b, count + 1);
            }
        }
        //把每一个键值对转换成Node对象,并加入nodes集合
        for (Map.Entry<Byte, Integer> each : counts.entrySet()) {
            nodes.add(new Node(each.getKey(), each.getValue()));
        }
        return nodes;
    }

    private static Node createHuffmanTree(List<Node> nodes) {
        /**
         *@MethodName createHuffmanTree
         *@Description TODO 创建哈夫曼树
         *@Author SSRS
         *@Date 2020-10-31 13:21
         *@Param [nodes]
         *@ReturnType Java_note.Algorithm.HuffmanCode.Node
         */
        while (nodes.size() > 1) {
            //排序
            Collections.sort(nodes);
            //取最小两个二叉树
            Node leftNode = nodes.get(0);
            Node rightNode = nodes.get(1);
            //创建新的二叉树(他的根节点没有data,只有权值)
            Node parent = new Node(null, leftNode.weight + rightNode.weight);
            parent.left = leftNode;
            parent.right = rightNode;
            //删除已用结点
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            //加入新节点
            nodes.add(parent);
        }
        return nodes.get(0);
    }

    static Map<Byte, String> codes = new HashMap<Byte, String>();

    private static Map<Byte, String> getCodes(Map<Byte, String> codes, Node node, String code, StringBuilder stringBuilder) {
        StringBuilder stringBuilder1 = new StringBuilder(stringBuilder);
        stringBuilder1.append(code);
        if (node != null) {
            if (node.data == null) {
                getCodes(codes, node.left, "0", stringBuilder1);
                getCodes(codes, node.right, "1", stringBuilder1);
            } else {
                codes.put(node.data, stringBuilder1.toString());
            }
        }
        return codes;
    }

    private static byte[] bytesArrays(String string) {
        int len;//bytes数组长度
        if (string.length() % 8 == 0) {
            len = string.length() / 8;
        } else {
            len = string.length() / 8 + 1;
        }
        int index = 0;//bytes角标
        byte[] huffmanCodeBytes = new byte[len];
        for (int i = 0; i < string.length(); i += 8) {
            String strTemp;
            if (i + 8 > string.length()) {
                strTemp = string.substring(i);
            } else {
                strTemp = string.substring(i, i + 8);
            }
            //转换
            huffmanCodeBytes[index] = (byte) Integer.parseInt(strTemp, 2);
            index++;
        }
        return huffmanCodeBytes;
    }

    public static String createHuffmanCode(byte[] bytes) {
        /**
         *@MethodName createHuffmanCode
         *@Description TODO 得到哈夫曼转码
         *@Author SSRS
         *@Date 2020-10-31 20:15
         *@Param [bytes]
         *@ReturnType java.lang.String
         */
        Node root = createHuffmanTree(getNodes(bytes));

        StringBuilder stringBuilder = new StringBuilder();

        getCodes(codes, root, "", stringBuilder);

        String str = "";
        for (Byte each : bytes) {
            str += codes.get(each);
        }
        return str;
    }

    public byte[] zipbytes(byte[] bytes) {
        return bytesArrays(createHuffmanCode(bytes));
    }

    //解压缩
    private String byteToBitString(boolean flag, byte b) {
        /**
         *@MethodName byteToBitString
         *@Description TODO 将一个byte转成二进制字符串
         *@Author SSRS
         *@Date 2020-10-31 20:56
         *@Param [flag 正数为true要补高位, b]
         *@ReturnType java.lang.String
         */
        int temp = b;//转int
        //如果是正数还要补高位
        if (flag) {
            temp |= 256;//按位与 256 是 1 0000 0000 | 0000 0001=》1 0000 0001
        }
        String str = Integer.toBinaryString(temp);//返回的是二进制的补码

        if (flag) {
            return str.substring(str.length() - 8);
        } else {
            return str;
        }

    }

    public byte[] rezip(byte[] huffmanbytes) {
        StringBuilder stringBuilder = new StringBuilder();//储存二进制字符串
        //全部转成二进制字符串
        for (int i = 0; i < huffmanbytes.length; i++) {
            //如果是最后一个可能不满8位就转码的那个就不用补高位无论正负,因此要判断
            boolean flag = (i == huffmanbytes.length - 1);
            stringBuilder.append(byteToBitString(!flag, huffmanbytes[i]));
        }

        //把哈夫曼编码表反向
        Map<String, Byte> map = new HashMap<String, Byte>();
        for (Map.Entry<Byte, String> each : codes.entrySet()) {
            map.put(each.getValue(), each.getKey());
        }

        List<Byte> list = new ArrayList();//解压缩后的语句的储存位置
        for (int i = 0; i < stringBuilder.length(); ) {
            int count = 1;//计数,计每一个字符的二进制字符位数
            boolean flag = true;
            Byte b = null;

            //找字符对应哈夫曼编码
            while (flag) {
                String key = stringBuilder.substring(i, i + count);
                b = map.get(key);
                if (b == null) {
                    count++;
                } else {//匹配成功
                    flag = false;
                }
            }
            list.add(b);
            i = i + count;
        }
        byte b[] = new byte[list.size()];
        for (int i = 0; i < b.length; i++) {
            b[i] = list.get(i);
        }
        return b;
    }

    public void preOrder(Node root) {
        if (root != null) {
            root.preOrder();
        } else {
            System.out.println("二叉树为空。");
        }
    }
}

 

 本文所有概念性描述及图示均来自王道考研数据结构一书

  • 7
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
的创建过程可以分成以下几步: 1. 统计字符出现频率并存储为一个数组或链表 2. 将数组或链表按照出现频率从小到大进行排序 3. 取出频率最小的两个节点,合并成一个新的节点,其权值为两个节点的权值之和 4. 将新节点插入到数组或链表中,保持有序 5. 重复步骤 3 和 4,直到只剩下一个节点,即为哈的根节点 以下是一个简单的 C 语言程序实现: ```c #include <stdio.h> #include <stdlib.h> // 哈节点定义 typedef struct { char data; // 节点存储的字符 int weight; // 节点的权值(出现频率) int parent, lchild, rchild; // 父节点、左子节点、右子节点的下标 } HuffmanNode, *HuffmanTree; // 创建哈 void createHuffmanTree(HuffmanTree *tree, int n) { // 创建 n 个叶子节点 *tree = (HuffmanTree) malloc(sizeof(HuffmanNode) * (2 * n - 1)); for (int i = 0; i < n; i++) { getchar(); printf("请输入第 %d 个字符及其出现频率(格式:字符 频率):", i + 1); scanf("%c%d", &((*tree)[i].data), &((*tree)[i].weight)); (*tree)[i].parent = -1; (*tree)[i].lchild = -1; (*tree)[i].rchild = -1; } // 创建 n-1 个非叶子节点 for (int i = n; i < 2 * n - 1; i++) { int min1 = -1, min2 = -1; for (int j = 0; j < i; j++) { if ((*tree)[j].parent == -1) { if (min1 == -1) { min1 = j; } else if (min2 == -1) { min2 = j; } else { if ((*tree)[j].weight < (*tree)[min1].weight) { min2 = min1; min1 = j; } else if ((*tree)[j].weight < (*tree)[min2].weight) { min2 = j; } } } } (*tree)[min1].parent = i; (*tree)[min2].parent = i; (*tree)[i].lchild = min1; (*tree)[i].rchild = min2; (*tree)[i].weight = (*tree)[min1].weight + (*tree)[min2].weight; } } int main() { int n; printf("请输入字符个数:"); scanf("%d", &n); HuffmanTree tree; createHuffmanTree(&tree, n); // 输出哈的结构 printf("哈的结构如下:\n"); for (int i = 0; i < 2 * n - 1; i++) { printf("%c %d %d %d %d\n", tree[i].data, tree[i].weight, tree[i].parent, tree[i].lchild, tree[i].rchild); } return 0; } ``` 注意,在以上代码中,我们假设输入的字符都是单字节字符。如果需要支持多字节字符,需要对输入和存储方式进行相应的修改。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

SS上善

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

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

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

打赏作者

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

抵扣说明:

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

余额充值