哈夫曼树以及哈夫曼编码

一、问题描述

构造一颗包含\(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过不了)。

代码地址:
码云

posted @ 2022-06-04 20:17  nofind  阅读(54)  评论(0编辑  收藏  举报