贪心算法-哈夫曼编码

时间:2021-09-08 11:10:11

算法思想:贪心算法

实际问题:活动安排问题

编写语言:Java


问题描述

  哈夫曼编码(Huffman Coding):又称霍夫曼编码,是由 Huffman 于1952年提出一种编码方法,是可变字长编码的一种。哈夫曼编码完全依据字符出现概率来构造异字头的平均长度最短的码字,有时被称为最佳编码,一般就叫做 Huffman 编码。通俗的讲,哈夫曼编码就是出现得越多的内容编码越短 ,出现频率越少的内容编码越长。对于只有一个待编码内容(如对单个字母编码)的情况,就不需要使用哈夫曼编码,用了反而显得多余。故哈夫曼编码的使用范围应该为内容有两个或者以上。
  哈夫曼树(Huffman Tree):给定作为 n 个叶子结点,其各带 1 个权值(共 n 个),构造一棵二叉树,若该树的带权路径长度达到最小,则称这样的二叉树为最优二叉树,也称为哈夫曼树。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。哈夫曼树的构造方式为自底向上构造,即先构造叶子节点,在构造父节点,最后构造根节点。

算法构造

  1. 对待编码的内容按照权值大小从小到大排序。
  2. 从已排序的内容列表中选取两个权值最小的内容。组成子节点。并将两者的权值想加,构成新的节点,但此节点无内容。
  3. 重复 1-2 步,有内容的组成叶子节点,无内容的不构成叶子节点。
  4. 对于最终构成的哈夫曼树,根节点不算入编码,左节点编码为 0,右节点编码为 1。
  5. 哈夫曼编码的解码过程是按照哈夫曼树遍历,完成一个内容解码就重新遍历哈夫曼树。

Java 代码

import java.util.Scanner;
import java.util.LinkedList;
import java.util.Collections;

public class HuffmanCode {
    /**
     * 存放节点的容器
    */
    private static LinkedList<HuffmanNode> huffmanList =
        new LinkedList<>();

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        //待编码元素个数
        System.out.print("请输入待编码的节点个数:");
        int n = input.nextInt();

        System.out.println("请输入节点的内容及其频数:");
        for(int i = 0; i < n; i++) {
            String s = input.next();
            int v = input.nextInt();
            huffmanList.add(new HuffmanNode(v, s));
        }

        huffCode();
        decode(huffmanList.get(0), "");
    }

    /**
     * 哈夫曼编码
    */
    public static void huffCode() {
        if(huffmanList.size() == 1) {
            return;
        }

        while(huffmanList.size() > 1) {
            /**
             * 贪心算法核心,排序后每次取最小的两个数
             * 每取两个数之后就得重新排序,直到只剩下一个元素
            */
            Collections.sort(huffmanList);

            /**
             * 这一步中隐含着一个规则,较小的为左子树,较大的为右子树。
             * 手画哈夫曼树时注意一下,构造的树和最后输出的结果就完全一致了。
            */
            HuffmanNode node = new HuffmanNode(huffmanList.get(0),
                                               huffmanList.get(1));
            //去掉两个子节点,加入父节点。
            //子节点虽然不在列表中,但并未被回收,因为在父节点中有引用
            huffmanList.remove();
            huffmanList.remove();
            huffmanList.add(node);
        }
    }

    /**
     * 解码算法
     * 打印顺序是从左到右打印哈夫曼树中的叶子节点
    */
    public static void decode(HuffmanNode h, String code) {
        //如果是叶子节点,就输出,只有叶子节点中存储着文本内容,父节点中没有
        if(h.lChild == null && h.rChild == null)
        {
            System.out.println("元素 " + h.name + "的编码为:" + code);
            return;
        }
        //如果是父节点,就遍历左右子树(编码遵循左0右1),直到搜寻到叶子节点
        //根据哈夫曼树的构造方式,父节点一定同时有左右子树,无须增加if判断
        decode(h.lChild, code + "0");
        decode(h.rChild, code + "1");
        
    }
}

/**
     * 哈夫曼节点
    */
class HuffmanNode implements Comparable<HuffmanNode> {
    int value;
    String name;
    HuffmanNode lChild = null;
    HuffmanNode rChild = null;

    public HuffmanNode() {

    }

    public HuffmanNode(int v,String s) {
        value = v;
        name = s;
    }

    /**
     * 这里是浅复制,lChild和l都指向一个内存区域,
     * rChild和r都指向同一个内存区域
    */
    public HuffmanNode(HuffmanNode l,HuffmanNode r) {
        lChild = l;
        rChild = r;
        value = lChild.value + rChild.value;
    }

    @Override
    public int compareTo(HuffmanNode node1) {
        if (value<node1.value) {
            return -1;
        }
        else if (value == node1.value) {
            return 0;
        }
        else {
            return 1;
        }
    }
}

结果示例

贪心算法-哈夫曼编码