• 哈夫曼树以及哈夫曼编码


    一、问题描述

    构造一颗包含\(n\)个叶子节点的\(k\)叉树,其中第\(i\)个叶子节点带有权值\(w_i\),要求最小化\(\sum w_i*l_i\),其中\(l_i\)表示第\(i\)个叶子节点到根节点的距离。

    二、算法描述

    运用贪心的思想,权值大的叶子结点的深度一定要小。
    先考虑\(k=2\)的情况:
    我们不难想出一种贪心算法。
    1.建立一个小根堆,插入这\(n\)个叶子节点的权值。
    2.从堆中取出最小的两个权值\(w_1\)\(w_2\),令\(ans+=w_1+w_2\)
    3.建立一个新的节点\(p\),权值为\(w_1+w_2\),令\(p\)成为取出的两个节点的父亲。
    4.在堆中插入权值\(w_1+w_2\)
    5.重复上述\(2\)\(4\)步,直到堆得大小为\(1\)
    Luogu P1090 合并果子

    点击查看代码
    import java.util.ArrayList;
    import java.util.PriorityQueue;
    import java.util.Scanner;
    
    /**
     * @author dongyudeng
     */
    public class BinaryHuffmanTree {
        public static void main(String[] args) {
            Scanner scanner = new Scanner(System.in);
            int num = scanner.nextInt();
            ArrayList<Integer> values = new ArrayList<>();
            for (int i = 0; i < num; i++) {
                values.add(scanner.nextInt());
            }
            PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(values);
            Integer ans = 0;
            while (priorityQueue.size() > 1) {
                Integer value1 = priorityQueue.poll(), value2 = priorityQueue.poll();
                ans += value1 + value2;
                priorityQueue.add(value1 + value2);
            }
            System.out.println(ans);
        }
    }
    

    接着再扩展到\(k>2\)的情况:
    我们的第一个想法必定是套用上述贪心算法,只是改为每次从堆中取出最小的\(k\)个值。但是在最后一轮循环中,堆的大小在\(2~k-1\)之间,根节点的子节点数小于\(k\),显然不是最优解,因此我们补加一些权值为\(0\)的叶子节点,使得叶子节点的个数\(n\)满足\((n-1)\mod (k-1)=0\),这么做是为了让子节点数小于\(k\)的情况出现在最底层(权值为\(0\)的叶子结点显然是在最底层的),从而保证正确性。
    注:
    \((n-1)\mod (k-1)=0\)推导:
    设度为\(k\)的节点数为\(m\),总节点数为\(l\)
    补了权值为\(0\)的节点后,除了叶子结点外的所有节点都满度,所以\(n+m=l\)
    又因为\(l-1=k*m\)(入边\(=\)出边),可得\(n-1=(k-1)m\),得证。

    哈夫曼树的应用:哈夫曼编码
    Luogu P2168 荷马史诗
    本题所构建的即为哈夫曼编码,我们将\(n\)个值作为叶子结点的权值,求出哈夫曼树,将每个节点的出边标上\(0\)\(k-1\),即可构成一颗\(trie\)树。
    观察这颗\(trie\)树,单词\(i\)的编码为从根节点到叶子结点\(i\)的路径,又因为单词都是叶子结点,恰好满足了一个单词不是另一个的前缀。
    此外,本题要求\(trie\)的深度最小,我们在合并时优先合并已合并次数最少的节点即可。

    点击查看代码
    import java.util.ArrayList;
    import java.util.Objects;
    import java.util.PriorityQueue;
    import java.util.Scanner;
    
    /**
     * @author dongyudeng
     * Luogu P2168 荷马史诗
     */
    public class HuffmanTree {
    
        public static void main(String[] args) {
            Scanner scanner = new Scanner(System.in);
            int num = scanner.nextInt(), k = scanner.nextInt();
            ArrayList<Long> values = new ArrayList<>();
            for (int i = 0; i < num; i++) {
                values.add(scanner.nextLong());
            }
            while ((num - 1) % (k - 1) != 0) {
                values.add(0L);
                num++;
            }
            PriorityQueue<TreeNode> priorityQueue = new PriorityQueue<>();
            for (Long value : values) {
                priorityQueue.add(new TreeNode(0, value));
            }
            long ans = 0;
            while (priorityQueue.size() >= k) {
                TreeNode[] nodes = new TreeNode[k];
                for (int i = 0; i < k; i++) nodes[i] = priorityQueue.poll();
                int maxCnt = 0;
                long value = 0;
                for (TreeNode node : nodes) {
                    value += node.getValue();
                    maxCnt = Math.max(maxCnt, node.getCnt());
                }
                ans += value;
                priorityQueue.add(new TreeNode(maxCnt + 1, value));
            }
            System.out.println(ans);
            int maxCnt = 0;
            for (TreeNode node : priorityQueue) maxCnt = Math.max(maxCnt, node.getCnt());
            System.out.println(maxCnt);
        }
    }
    
    class TreeNode implements Comparable<TreeNode> {
        private int cnt;
    
        private long value;
    
        public TreeNode(int cnt, long value) {
            this.cnt = cnt;
            this.value = value;
        }
    
        public int getCnt() {
            return cnt;
        }
    
        public void setCnt(int cnt) {
            this.cnt = cnt;
        }
    
        public long getValue() {
            return value;
        }
    
        public void setValue(long value) {
            this.value = value;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            TreeNode treeNode = (TreeNode) o;
            return cnt == treeNode.cnt && value == treeNode.value;
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(cnt, value);
        }
    
        @Override
        public int compareTo(TreeNode o) {
            //equal
            if (this.value == o.value && this.cnt == o.cnt) return 0;
            //less or greater
            boolean isLess = this.value < o.value || (this.value == o.value && this.cnt < o.cnt);
            if (isLess) return -1;
            else return 1;
        }
    }
    

    扩展:
    关于Luogu P6033 合并果子(加强版)
    桶排序+队列模拟(java过不了)。

    代码地址:
    码云

  • 相关阅读:
    微信平台的开发与集成
    自定义控件定义样式
    Android万能分辨率适应法
    Openfire配置过程,以及与php交互注意事项。
    Android 最近的一些新的功能
    自定义Ratingbar 评分控件
    解决android有的手机拍照后上传图片被旋转的问题
    Fragment 嵌套使用 Activity has been destoryed
    实现图文混排方法 类似于网易那样的
    Android 自定义View及其在布局文件中的使用示例
  • 原文地址:https://www.cnblogs.com/nofind/p/16342651.html
Copyright © 2020-2023  润新知