贪心法/哈夫曼编码

贪心法

贪心准则:每次从队列中取出最大和次大的两个结点,权重相加之后构成新的结点后放入队列中
最优性原理:最优子结构问题,子问题也是哈夫曼编码问题,对子问题的求解等价于对大问题的求解

哈夫曼编码

哈夫曼树只有度为0的结点和度为2的结点
已知公式n0=n2+1,叶子结点有n个时,度为2的结点有n-1个
使用静态链表存储哈夫曼树时需要存储n个叶子结点,n-1个度为2的结点,共需要2n-1长度的静态链表
所谓静态链表即用顺序表(数组)模拟链表,使用数组下标模拟结点地址指针
哈夫曼树是最优二叉树,最优二叉树是带权路径长度最短的树
过程:每次从队列中取出最大和次大的两个结点,权重相加之后构成新的结点后放入队列中

代码

package xcrj.kchalgorithm.greedy;

import java.util.*;

/**
 * 哈夫曼编码:最小带权路径长度的二叉树,最优二叉树
 * 字符集为{d1, d2, …, dn}
 * 字符对应出现频率为{w1, w2, …, wn}
 * 不等长0 1编码方案
 */
public class HuffmanCoding {
    /*哈夫曼树结点*/
    static class HuffmanTreeNode {
        // 结点字符
        private char data;
        // 权值
        private int weight;
        // 父节点
        private int parent;
        // 左孩子结点
        private int leftChild;
        // 右孩子结点
        private int rightChild;
    }

    /**
     * 优先队列元素
     * 作用把哈夫曼树中的结点放到优先级队列中
     */
    static class Node implements Comparable<Node> {
        // 对应哈夫曼树中结点位置
        private int no;
        // 字符
        private char data;
        // 权值
        private int weight;

        /*创建小根堆*/
        @Override
        public int compareTo(Node o) {
            return this.weight >= o.weight ? 1 : -1;
        }
    }

    // 字符数量
    private static int n = 5;
    /**
     * 静态链表数据结构存储树
     * 问:为什么静态链表容量是2 * n - 1
     * 答:哈夫曼树只有度为0的叶子结点和度为2的结点,
     * 已知公式n0=n2+1,叶子结点=n,所以非叶子结点数量=度为2的结点数量=n-1
     * 所以哈夫曼树共有结点 n+n-1=2*n-1
     */
    private static HuffmanTreeNode[] huffmanTreeNodes = new HuffmanTreeNode[2 * n - 1];

    static {
        // 数组里面预留了引用空间,但没有具体引用对象
        for (int i = 0; i < 2 * n - 1; i++) {
            huffmanTreeNodes[i] = new HuffmanTreeNode();
        }
    }

    // 存放每个字符的哈夫曼编码
    private static Map<Character, String> huffmanCode = new HashMap<>(n);

    /**
     * 创建哈夫曼树
     * 每次从队列中取出最大和次大的两个结点
     * 权重相加之后构成新的结点后放入队列中
     */
    public static void createHuffmanTree(char[] datas, int[] weights) {
        // 初始化叶子结点数据和权重
        for (int i = 0; i < n; i++) {
            huffmanTreeNodes[i].data = datas[i];
            huffmanTreeNodes[i].weight = weights[i];
        }

        // 所有哈夫曼结点:设置2*n-1个哈夫曼结点的指针域
        for (int i = 0; i < 2 * n - 1; i++) {
            huffmanTreeNodes[i].parent = -1;
            huffmanTreeNodes[i].leftChild = -1;
            huffmanTreeNodes[i].rightChild = -1;
        }

        // 优先级队列
        PriorityQueue<Node> priorityQueue = new PriorityQueue<>(n);

        // n个叶子结点:将n个叶子结点进优先级队列,构建小根堆
        for (int j = 0; j < n; j++) {
            Node node = new Node();
            node.no = j;
            node.data = huffmanTreeNodes[j].data;
            node.weight = huffmanTreeNodes[j].weight;
            priorityQueue.add(node);
        }
        /**
         * n-1个非叶子结点
         * 构造哈夫曼树的n-1个非叶子结点
         * 问:为什么是n-1个叶子结点?
         * 答:哈夫曼树只有度为0的叶子结点和度为2的结点,
         *    已知公式n0=n2+1,叶子结点=n,所以非叶子结点数量=度为2的结点数量=n-1
         */
        for (int k = n; k < 2 * n - 1; k++) {
            // 从优先级队列中取出 最大和次大的两个结点
            Node leftChild = priorityQueue.remove();
            Node rightChild = priorityQueue.remove();
            // 放入哈夫曼树,静态链表存储“最优二叉树”
            huffmanTreeNodes[k].weight = leftChild.weight + rightChild.weight;
            huffmanTreeNodes[k].leftChild = leftChild.no;
            huffmanTreeNodes[k].rightChild = rightChild.no;
            huffmanTreeNodes[leftChild.no].parent = k;
            huffmanTreeNodes[rightChild.no].parent = k;
            // 将权重相加后的结点 放入优先级队列
            Node node = new Node();
            node.no = k;
            node.weight = leftChild.weight + rightChild.weight;
            priorityQueue.add(node);
        }
    }

    /*构造哈夫曼编码*/
    public static void createHuffmanCode() {
        // n个叶子结点才有哈夫曼编码
        for (int i = 0; i < n; i++) {
            // 存储编码结果
            StringBuilder code = new StringBuilder(5);
            code.append("");
            // 记录当前游标和父游标
            int nowNo = i;
            int preNo = huffmanTreeNodes[nowNo].parent;
            // 从叶子结点往前根节点走
            while (preNo != -1) {
                // 左孩子,左0
                if (huffmanTreeNodes[preNo].leftChild == nowNo) code.append("0");
                    // 右孩子,右1
                else code.append("1");
                // 继续往根节点走
                nowNo = preNo;
                preNo = huffmanTreeNodes[nowNo].parent;
            }
            // 存储每个叶子结点的哈夫曼编码
            huffmanCode.put(huffmanTreeNodes[i].data, code.reverse().toString());
        }
    }

    public static void main(String[] args) {
        char[] datas = {'A', 'B', 'C', 'D', 'E'};
        int[] weights = {4, 2, 1, 7, 3};
        createHuffmanTree(datas, weights);
        createHuffmanCode();
        System.out.println(huffmanCode);
    }
}

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值