最优前缀编码

Posted powerkeke

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最优前缀编码相关的知识,希望对你有一定的参考价值。

1. 问题

通常的编码方法有固定长度编码和不等长度编码两种。这是一个设计最优编码方案的问题,目的是使总码长度最短。这个问题利用字符的使用频率来编码,是不等长编码方法,使得经常使用的字符编码较短,不常使用的字符编码较长。如果采用等长的编码方案,假设所有字符的编码都等长,则表示 n 个不同的字符需要 log n 位。例如,3 个不同的字符 ab、c,至少需要 2 位二进制数表示,a 00,b 01,c 10。如果每个字符的使用频率相等,固定长度编码是空间效率最高的方法。

不等长编码方法需要解决两个关键问题:

(1)编码尽可能短

我们可以让使用频率高的字符编码较短,使用频率低的编码较长,这种方法可以提高压缩率,节省空间,也能提高运算和通信速度。即频率越高,编码越短。

(2)不能有二义性

例如,ABCD 四个字符如果编码如下。

A:0B:1C:01D:10

那么现在有一列数 0110,该怎样翻译呢?是翻译为 ABBA,ABD,CBA,还是 CD?那么如何消除二义性呢?解决的办法是:任何一个字符的编码不能是另一个字符编码的前缀,即前缀码特性。1952 ,数学家 D.A.Huffman 提出了根据字符在文件中出现的频率,01 的数字串表示各字符的最佳编码方式,称为哈夫曼(Huffman)编码。哈夫曼编码很好地解决了上述两个关键问题,被广泛应用于数据压缩,尤其是远距离通信和大容量数据存储方面,常用的JPEG 图片就是采用哈夫曼编码压缩的。 

 

2. 解析

哈夫曼编码的基本思想是以字符的使用频率作为权构建一棵哈夫曼树,然后利用哈夫曼树对字符进行编码。构造一棵哈夫曼树,是将所要编码的字符作为叶子结点,该字符在文件中的使用频率作为叶子结点的权值,以自底向上的方式,通过 n1 次的“合并”运算后构造出的一棵树,核心思想是权值越大的叶子离根越近。

哈夫曼算法采取的贪心策略是每次从树的集合中取出没有双亲且权值最小的两棵树作为左右子树 

3. 分析

n + (n + 1) + (n + 2) + ... +(n + (n -2)) = (n -1) *(3n - 2) /2=O(N²)

4. 源码

/*
project name:哈夫曼树
Time Complexity: O(n²)
*/
#include <iostream>
using namespace std;

//最大字符编码数组长度
#define MAXCODELEN 100
//最大哈夫曼节点结构体数组个数
#define MAXHAFF 100
//最大哈夫曼编码结构体数组的个数
#define MAXCODE 100
#define MAXWEIGHT 10000;

typedef struct Haffman {
    //权重
    int weight;
    //字符
    char ch;
    //父节点
    int parent;
    //左儿子节点
    int leftChild;
    //右儿子节点
    int rightChild;
} HaffmaNode;

typedef struct Code {
    //字符的哈夫曼编码的存储
    int code[MAXCODELEN];
    //从哪个位置开始
    int start;
} HaffmaCode;

HaffmaNode haffman[MAXHAFF];
HaffmaCode code[MAXCODE];

void buildHaffman(int all) {
    //哈夫曼节点的初始化之前的工作, weight为0,parent,leftChile,rightChile都为-1
    for (int i = 0; i < all * 2 - 1; ++i) {
        haffman[i].weight = 0;
        haffman[i].parent = -1;
        haffman[i].leftChild = -1;
        haffman[i].rightChild = -1;
    }
    std::cout << "请输入需要哈夫曼编码的字符和权重大小" << std::endl;
    for (int i = 0; i < all; i++) {
        std::cout << "请分别输入第个" << i << "哈夫曼字符和权重" << std::endl;
        std::cin >> haffman[i].ch;
        std::cin >> haffman[i].weight;
    }
    //每次找出最小的权重的节点,生成新的节点,需要all - 1 次合并
    int x1, x2, w1, w2;
    for (int i = 0; i < all - 1; ++i) {
        x1 = x2 = -1;
        w1 = w2 = MAXWEIGHT;
        //注意这里每次是all + i次里面便利
        for (int j = 0; j < all + i; ++j) {
            //得到最小权重的节点
            if (haffman[j].parent == -1 && haffman[j].weight < w1) {
                //如果每次最小的更新了,那么需要把上次最小的给第二个最小的
                w2 = w1;
                x2 = x1;
                x1 = j;
                w1 = haffman[j].weight;
            }
            //这里用else if而不是if,是因为它们每次只选1个就可以了。
            else if (haffman[j].parent == -1 && haffman[j].weight < w2) {
                x2 = j;
                w2 = haffman[j].weight;
            }
        }
        //么次找到最小的两个节点后要记得合并成一个新的节点
        haffman[all + i].leftChild = x1;
        haffman[all + i].rightChild = x2;
        haffman[all + i].weight = w1 + w2;
        haffman[x1].parent = all + i;
        haffman[x2].parent = all + i;
        std::cout << "x1 is" << x1 << " x1 parent is" << haffman[x1].parent
                  << " x2 is" << x2 << " x2 parent is " << haffman[x2].parent
                  << " new Node is " << all + i << "new weight is"
                  << haffman[all + i].weight << std::endl;
    }
}
//打印每个字符的哈夫曼编码
void printCode(int all) {
    //保存当前叶子节点的字符编码
    HaffmaCode hCode;
    //当前父节点
    int curParent;
    //下标和叶子节点的编号
    int c;
    //遍历的总次数
    for (int i = 0; i < all; ++i) {
        hCode.start = all - 1;
        c = i;
        curParent = haffman[i].parent;
        //遍历的条件是父节点不等于-1
        while (curParent != -1) {
            //我们先拿到父节点,然后判断左节点是否为当前值,如果是取节点0
            //否则取节点1,这里的c会变动,所以不要用i表示,我们用c保存当前变量i
            if (haffman[curParent].leftChild == c) {
                hCode.code[hCode.start] = 0;
                std::cout << "hCode.code[" << hCode.start << "] = 0"
                          << std::endl;
            } else {
                hCode.code[hCode.start] = 1;
                std::cout << "hCode.code[" << hCode.start << "] = 1"
                          << std::endl;
            }
            hCode.start--;
            c = curParent;
            curParent = haffman[c].parent;
        }
        //把当前的叶子节点信息保存到编码结构体里面
        for (int j = hCode.start + 1; j < all; ++j) {
            code[i].code[j] = hCode.code[j];
        }
        code[i].start = hCode.start;
    }
}
int main() {
    std::cout << "请输入有多少个哈夫曼字符" << std::endl;
    int all = 0;
    std::cin >> all;
    if (all <= 0) {
        std::cout << "您输入的个数有误" << std::endl;
        return -1;
    }
    buildHaffman(all);
    printCode(all);
    for (int i = 0; i < all; ++i) {
        std::cout << haffman[i].ch << ": Haffman Code is:";
        for (int j = code[i].start + 1; j < all; ++j) {
            std::cout << code[i].code[j];
        }
        std::cout << std::endl;
    }
    return 0;
}

 

以上是关于最优前缀编码的主要内容,如果未能解决你的问题,请参考以下文章

(学习11)哈夫曼算法

算法分析与设计(work11)

数据结构之哈弗曼编码的(Huffman Coding)加密解密压缩

leetcode_1292. Maximum Side Length of a Square with Sum Less than or Equal to Threshold_[二维前缀和](代码片段

markdown 打字稿...编码说明,提示,作弊,指南,代码片段和教程文章

基于二叉树和双向链表实现限制长度的最优Huffman编码