数据结构与算法(Java)之哈夫曼树及其应用

哈夫曼树

package com.weeks.tree.huffmantree;

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

/**
 * @author 达少
 * @version 1.0
 *
 * 实现哈夫曼树(就是权值路径长度wpl最小的树)
 *
 */
public class HuffmanTree {
    public static void main(String[] args) {
        int[] arr = {13, 7, 8, 3, 29, 6, 1};
        Node root = createHuffmanTree(arr);
        preOrder(root);
    }

    //构建哈夫曼树
    public static Node createHuffmanTree(int[] arr){
        //创建List对象,存储构建哈夫曼树的二叉树
        List<Node> nodes = new ArrayList<>();
        //根据数组的值,构建哈夫曼树的结点,并将结点放到List中
        for (int i = 0; i < arr.length; i++) {
            nodes.add(new Node(arr[i]));
        }
        while(nodes.size() > 1) {//当nodes中只剩下一个元素时才完成哈夫曼树的创建
            //将nodes中的node按照升序排序
            Collections.sort(nodes);
            //取出nodes中俩棵最小的二叉树
            Node leftNode = nodes.get(0);
            Node rightNode = nodes.get(1);
            //将取出的两棵二叉树作为新创建的结点的左右子树
            Node parent = new Node(leftNode.getValue() + rightNode.getValue());
            parent.setLeft(leftNode);
            parent.setRight(rightNode);
            //将取出的两棵二叉树从nodes中删除
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            //将新创建的二叉树加入到nodes中
            nodes.add(parent);
        }
        return nodes.get(0);
    }

    //定义前序遍历方法,遍历哈夫曼树
    public static void preOrder(Node root){
        //判断书是否为空
        if(root == null){
            System.out.println("空树不能遍历!!!");
        }
        //遍历
        root.preOrder();
    }
}

//创建哈夫曼树结点(每一个单独的结点可以看作是最简单的一棵二叉树)
//因为建立哈夫曼树的过程中要比较结点的权值大小,所以为了方便比较要实现Comparable
//重写compareTo方法
class Node implements Comparable<Node> {
    private int value;
    private Node left;
    private Node right;

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

    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;
    }

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


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

    //前序遍历
    public void preOrder(){
        //判断当前结点是否为空
        if(this == null){
            return;
        }
        //先输出当前结点
        System.out.println(this);
        //递归遍历左子树
        if(this.left != null){
            this.left.preOrder();
        }
        //递归遍历右子树
        if(this.right != null){
            this.right.preOrder();
        }
    }
}

哈夫曼编码

package com.weeks.tree.huffmancode;

import java.util.*;

/**
 * @author 达少
 * @version 1.0
 *
 * 哈夫曼编码:
 * 以编码字符串"i like like like java do you like a java"为例
 * 1.找到每个字符出现的次数:d-1,y-1,u-1,v-2,o-2,i-4,l-4,k-4,i-5,a-5,空格-9
 * 2.将字符出现的次数作为权值构建哈夫曼树
 * 3.构建好哈夫曼树后,按照左子树为0,右子树为1,从根节点开始找到代表每个字符的路径就是
 *   哈夫曼编码
 *
 */
public class HuffmanCode {
    public static void main(String[] args) {
        String content = "i like like like java do you like a java";
        List<Node> nodes = getNodes(content);
        Node root = createHuffmanTree(nodes);
        root.preOrder();
        getCodes(root);
        System.out.println(huffmanCodes);
    }

    //获得构建哈夫曼树的结点
    public static List<Node> getNodes(String str){
        //将字符串转化字节数组
        byte[] bytes = str.getBytes();
        //创建存储节点的List
        List<Node> nodes = new ArrayList<>();
        HashMap<Byte, Integer> map = new HashMap<>();
        //统计每个字符出现的次数
        for(byte b : bytes){
            Integer count = map.get(b);
            if(count == null){
                map.put(b, 1);
            }else{
                map.put(b, count + 1);
            }
        }
        //构建二叉树的结点
        for (Map.Entry<Byte, Integer> entry: map.entrySet()){
            nodes.add(new Node(entry.getKey(), entry.getValue()));
        }

        return nodes;
    }

    //构建哈夫曼树
    public static Node createHuffmanTree(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.getCount() + rightNode.getCount());
            //设置新创建的二叉树左右指针域,不设置则在遍历时无法遍历
            parent.setLeft(leftNode);
            parent.setRight(rightNode);
            //将取出的二叉树冲nodes冲删除
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            //将新创建的二叉树加入nodes中
            nodes.add(parent);
        }
        return nodes.get(0);//返回根结点
    }

    //前序遍历
    public void preOrder(Node root){
        if(root == null){
            System.out.println("空树无法遍历");
        }else{
            root.preOrder();
        }
    }

    //生成的哈夫曼编码应该存储在一个map对象中,结构是Byte(字符):String(字符对应的哈夫曼编码)
    //所以定义一个Map属性用于存储字符对应的哈夫曼编码
    static Map<Byte, String> huffmanCodes = new HashMap<>();
    //在生成哈夫曼编码的过程中需要不断拼接,所以创建属性用于拼接哈夫曼编码
    static StringBuilder stringBuilder = new StringBuilder();
    //生成哈夫曼编码表

    /**
     * 功能是生成对应字符的哈夫曼编码
     * @param node 哈夫曼树的结点
     * @param code node对应的哈夫曼编码:如果是根结点就为空"", 左子结点为"0",右子结点为"1"
     * @param sb 传入的StringBuilder用于存储拼接的哈夫曼编码
     * @return
     */
    public static void getCodes(Node node, String code, StringBuilder sb){
        //新建一个StringBuilder临时存放拼接的哈夫曼编码
        StringBuilder stringBuilder2 = new StringBuilder(sb);
        //将当前的code加入stringBuilder2中
        stringBuilder2.append(code);
        if(node != null){//判断当前结点是否为空,如果不为空才能处理
            //判断当前结点是否为叶子结点
            if(node.getContent() == null){//非叶子结点
                //向左递归
                getCodes(node.getLeft(), "0", stringBuilder2);
                //向右递归
                getCodes(node.getRight(), "1", stringBuilder2);
            }else{//叶子结点
                //到叶子结点说明已经遍历到一个字符了,将该字符的哈夫曼编码加入huffmanCodes中
                huffmanCodes.put(node.getContent(), stringBuilder2.toString());
            }
        }
    }
    //重载getCodes方法方便调用,只传入根结点
    public static Map<Byte, String> getCodes(Node root){
        //判断根节点是否为空
        if(root == null){
            return null;
        }
        //左递归
        getCodes(root.getLeft(), "0", stringBuilder);
        //右递归
        getCodes(root.getRight(), "1", stringBuilder);

        return huffmanCodes;
    }
}

class Node implements Comparable<Node>{
    private Byte content;//代表字符
    private int count;//表示字符的权值(就是出现的次数)
    private Node left;//左指针域
    private Node right;//右指针域

    public Node(Byte content, int count){
        this.content = content;
        this.count = count;
    }

    public Byte getContent() {
        return content;
    }

    public void setContent(Byte content) {
        this.content = content;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    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;
    }

    @Override
    public String toString() {
        return "Node{" +
                "content=" + content +
                ", count=" + count +
                '}';
    }

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

    //前序遍历
    public void preOrder(){
        System.out.println(this);
        if(this.getLeft() != null){
            this.getLeft().preOrder();
        }
        if(this.getRight() != null){
            this.getRight().preOrder();
        }
    }
}

哈夫曼编码压缩

package com.weeks.tree.huffmancode;

import java.util.*;

/**
 * @author 达少
 * @version 1.0
 *
 * 哈夫曼编码:
 * 以编码字符串"i like like like java do you like a java"为例
 * 1.找到每个字符出现的次数:d-1,y-1,u-1,v-2,o-2,i-4,l-4,k-4,i-5,a-5,空格-9
 * 2.将字符出现的次数作为权值构建哈夫曼树
 * 3.构建好哈夫曼树后,按照左子树为0,右子树为1,从根节点开始找到代表每个字符的路径就是
 *   哈夫曼编码
 *
 */
public class HuffmanCode {
    public static void main(String[] args) {
        String content = "i like like like java do you like a java";
        //将字符串转化字节数组
        byte[] bytes = content.getBytes();
        //封装压缩过程
        byte[] huffmanZipBytes = huffmanZip(bytes);
        System.out.println("压缩后的字节数组:" + Arrays.toString(huffmanZipBytes));
        //分步完成哈夫曼压缩
        /*
        List<Node> nodes = getNodes(bytes);
        Node root = createHuffmanTree(nodes);
        root.preOrder();
        getCodes(root);
        System.out.println(huffmanCodes);
        byte[] zipBytes = zip(bytes, huffmanCodes);
        System.out.println(Arrays.toString(zipBytes));
         */
    }

    //获得构建哈夫曼树的结点
    public static List<Node> getNodes(byte[] bytes){
        //创建存储节点的List
        List<Node> nodes = new ArrayList<>();
        HashMap<Byte, Integer> map = new HashMap<>();
        //统计每个字符出现的次数
        for(byte b : bytes){
            Integer count = map.get(b);
            if(count == null){
                map.put(b, 1);
            }else{
                map.put(b, count + 1);
            }
        }
        //构建二叉树的结点
        for (Map.Entry<Byte, Integer> entry: map.entrySet()){
            nodes.add(new Node(entry.getKey(), entry.getValue()));
        }

        return nodes;
    }

    //构建哈夫曼树
    public static Node createHuffmanTree(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.getCount() + rightNode.getCount());
            //设置新创建的二叉树左右指针域,不设置则在遍历时无法遍历
            parent.setLeft(leftNode);
            parent.setRight(rightNode);
            //将取出的二叉树冲nodes冲删除
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            //将新创建的二叉树加入nodes中
            nodes.add(parent);
        }
        return nodes.get(0);//返回根结点
    }

    //前序遍历
    public void preOrder(Node root){
        if(root == null){
            System.out.println("空树无法遍历");
        }else{
            root.preOrder();
        }
    }

    //生成的哈夫曼编码应该存储在一个map对象中,结构是Byte(字符):String(字符对应的哈夫曼编码)
    //所以定义一个Map属性用于存储字符对应的哈夫曼编码
    static Map<Byte, String> huffmanCodes = new HashMap<>();
    //在生成哈夫曼编码的过程中需要不断拼接,所以创建属性用于拼接哈夫曼编码
    static StringBuilder stringBuilder = new StringBuilder();
    //生成哈夫曼编码表

    /**
     * 功能是生成对应字符的哈夫曼编码
     * @param node 哈夫曼树的结点
     * @param code node对应的哈夫曼编码:如果是根结点就为空"", 左子结点为"0",右子结点为"1"
     * @param sb 传入的StringBuilder用于存储拼接的哈夫曼编码
     * @return
     */
    public static void getCodes(Node node, String code, StringBuilder sb){
        //新建一个StringBuilder临时存放拼接的哈夫曼编码
        StringBuilder stringBuilder2 = new StringBuilder(sb);
        //将当前的code加入stringBuilder2中
        stringBuilder2.append(code);
        if(node != null){//判断当前结点是否为空,如果不为空才能处理
            //判断当前结点是否为叶子结点
            if(node.getContent() == null){//非叶子结点
                //向左递归
                getCodes(node.getLeft(), "0", stringBuilder2);
                //向右递归
                getCodes(node.getRight(), "1", stringBuilder2);
            }else{//叶子结点
                //到叶子结点说明已经遍历到一个字符了,将该字符的哈夫曼编码加入huffmanCodes中
                huffmanCodes.put(node.getContent(), stringBuilder2.toString());
            }
        }
    }
    //重载getCodes方法方便调用,只传入根结点
    public static Map<Byte, String> getCodes(Node root){
        //判断根节点是否为空
        if(root == null){
            return null;
        }
        //左递归
        getCodes(root.getLeft(), "0", stringBuilder);
        //右递归
        getCodes(root.getRight(), "1", stringBuilder);

        return huffmanCodes;
    }

    //使用哈夫曼编码将字节数组进行压缩
    /**
     * 将传入的字节数组利用哈夫曼编码进行压缩
     * @param bytes
     * @param huffmanCodes
     */
    public static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes){
        //新建一个StringBuilder对象,用于存储拼接所有字符的哈夫曼编码
        StringBuilder huffmanCodeStr = new StringBuilder();
        //获取哈夫曼编码字符串
        for(byte b : bytes){
            huffmanCodeStr.append(huffmanCodes.get(b));
        }
        //将huffmanCodeStr划分为每8个为一组转成byte(一位byte是8位),构建相应的byte数组
        int len = 0;
        //下面的if..else..也可以用一句话完成:len = (huffmanCodeStr.length+7) / 8;
        if(huffmanCodeStr.length() % 8 == 0){
            len = huffmanCodeStr.length() / 8;
        }else{
            len = huffmanCodeStr.length() / 8 + 1;
        }
        //创建byte数组,用于存储根据哈夫曼编码后的字节
        byte[] transform = new byte[len];
        int index = 0;//标记transform的下标
        //将huffmanCodeStr的每8为转成一个byte存储在transform中
        for (int i = 0; i < huffmanCodeStr.length(); i+=8) {
            //分两种情况,可能最后一次分组没有8个字符了
            if(i + 8 <= huffmanCodeStr.length()){//在huffmanCodeStr长度内
                transform[index++] = (byte)Integer.parseInt(huffmanCodeStr.substring(i, i + 8));
            }else{//超出长度直接截取到最后
                transform[index++] = (byte)Integer.parseInt(huffmanCodeStr.substring(i));
            }
        }
        return transform;
    }

    /**
     * 功能:将哈夫曼压缩过程封装为一个方法
     * @param bytes 原始的字节数组
     * @return 返回经过哈夫曼编码后的字节数组
     */
    public static byte[] huffmanZip(byte[] bytes){
        //根据传入的bytes获取构建哈夫曼树的二叉树结点
        List<Node> nodes = getNodes(bytes);
        //构建哈夫曼树
        Node huffmanTree = createHuffmanTree(nodes);
        //获取哈夫曼编码
        Map<Byte, String> codes = getCodes(huffmanTree);
        //压缩
        byte[] zip = zip(bytes, codes);
        return zip;
    }
}

class Node implements Comparable<Node>{
    private Byte content;//代表字符
    private int count;//表示字符的权值(就是出现的次数)
    private Node left;//左指针域
    private Node right;//右指针域

    public Node(Byte content, int count){
        this.content = content;
        this.count = count;
    }

    public Byte getContent() {
        return content;
    }

    public void setContent(Byte content) {
        this.content = content;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    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;
    }

    @Override
    public String toString() {
        return "Node{" +
                "content=" + content +
                ", count=" + count +
                '}';
    }

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

    //前序遍历
    public void preOrder(){
        System.out.println(this);
        if(this.getLeft() != null){
            this.getLeft().preOrder();
        }
        if(this.getRight() != null){
            this.getRight().preOrder();
        }
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值