美文网首页
数据结构与算法-哈弗曼编码

数据结构与算法-哈弗曼编码

作者: 收纳箱 | 来源:发表于2020-04-28 22:06 被阅读0次

1. 概念

哈夫曼树,即最优二叉树,带权路径长度最小的二叉树,经常应用于数据压缩。

在计算机信息处理中,“哈夫曼编码”是一种一致性编码法(又称“熵编码法”),用于数据的无损耗压缩

1.1 构建过程

哈夫曼树的构建过程:

每次在所有未使用结点(白底)中先找到最小的两个(红底),然后构建一个双亲节点指向他们。

哈弗曼树的构建过程

2. 实现

2.1 结构

因为知道叶子节点个数,可以推算出总体的结点个数。使用顺序存储方式,使用索引访问更方便。

设叶子结点有n个,度为1的结点有x个,度为2的结点有y个。每个结点都是双亲节点的度,根结点除外,所以用度计算总结点数为x + 2 * y + 1
\begin{equation}\begin{split} x + 2 * y + 1 &= n + x + y \\ y &= n - 1 \end{split}\end{equation}
哈夫曼数中,不存在度为1的节点,即x=0
n + x + y = n + 0 + n - 1 = 2n - 1

// 构建哈弗曼树的结点
typedef struct HaffNode {
    int weight;         // 权值
    int flag;           // 是否使用过
    int parent;         // 父节点索引
    int leftChild;      // 左儿子索引
    int rightChild;     // 右儿子索引
} HaffNode;

// 存放哈夫曼编码的数据元素结构
typedef struct Code {
    int bit[MaxBit];    // 固定大小的数组
    int length;         // 编码的长度
    int weight;         // 字符的权值
} Code;

2.2 构建哈夫曼树

// 1.根据权重值,构建哈夫曼树
// {2,4,5,7} n=4
void Haffman(int *weight, int n, HaffNode *haffTree) {
    /*
     1.初始化
     设叶子结点有n个,度为1的结点有x个,度为2的结点有y个
     每个结点都是双亲节点的度,根结点除外,所以用度计算总结点数为x + 2 * y + 1
     x + 2 * y + 1 = n + x + y => y = n - 1
     哈夫曼数中,不存在度为1的节点,n + x + y = n + y = 2n - 1
    */
    for (int i = 0; i < 2*n-1; i++) {
        if (i < n) {
            haffTree[i].weight = weight[i]; // 有权值的叶子节点
        } else {
            haffTree[i].weight = 0; // 非叶子节点
        }
        // 其他属性初始化
        haffTree[i].flag = 0;
        haffTree[i].parent = haffTree[i].leftChild = haffTree[i].rightChild = -1;
    }
    
    // 2.构建哈夫曼树
    int m1,m2; // 记录最小的两个值
    int x1,x2; // 最小两个值的索引
    // 总结点数2n-1,我们要构建的双亲节点只有2n-1 - n = n-1个
    for (int i = 0; i < n - 1; i++) {
        m1 = m2 = MaxValue; // 先赋值为最大值,然后找更小的值,后续存储m1<m2
        x1 = x2 = 0; // 索引重置
        // 循环找出所有权重中,最小的二个值
        // 因为添加了新的双亲结点,所以遍历长度会增加
        for (int j = 0; j< n + i; j++) {
            if (haffTree[j].weight < m1 && haffTree[j].flag == 0) { // cur<m1<m2
                m2 = m1;
                x2 = x1;
                m1 = haffTree[j].weight;
                x1 = j;
            } else if(haffTree[j].weight < m2 && haffTree[j].flag == 0) { // m1<cur<m2
                m2 = haffTree[j].weight;
                x2 = j;
            }
        }
        
        // 3.将找出的两棵权值最小的子树合并为一棵子树
        // 标记两个结点已使用
        haffTree[x1].flag = 1;
        haffTree[x2].flag = 1;
        // 存储双亲结点索引
        haffTree[x1].parent = n + i;
        haffTree[x2].parent = n + i;
        // 设置双亲结点
        haffTree[n + i].leftChild = x1;
        haffTree[n + i].rightChild = x2;
        // 计算双亲结点的权值
        haffTree[n + i].weight = haffTree[x1].weight + haffTree[x2].weight;
    }
}

2.3 构建哈夫曼编码数组

/*
 2.哈夫曼编码
 由n个结点的哈夫曼树haffTree构造哈夫曼编码haffCode
 //{2,4,5,7}
 */
void HaffmanCode(HaffNode *haffTree, int n, Code *haffCode) {
    // 1.创建一个结点cd,辅助存储信息
    Code cd;
    int child, parent;
    // 2.求n个叶结点的哈夫曼编码
    for (int i = 0; i < n; i++) {
        cd.length = 0; // 从0开始计数
        cd.weight = haffTree[i].weight; // 取得编码对应权值的字符
        child = i; // 设置当前叶子结点为孩子
        parent = haffTree[child].parent; // 找到叶子节点的双亲结点
        
        // 因为起点我们只能从叶子节点开始,向上查找
        while (parent != -1) { // 通过-1判断是否还有双亲结点
            if (haffTree[parent].leftChild == child) // 判断当前结点是左孩子还是右孩子
                cd.bit[cd.length] = 0; // 左孩子结点编码0
            else
                cd.bit[cd.length] = 1; // 右孩子结点编码1
            cd.length++; // 编码长度自增
            child = parent; // 当前双亲结点成为孩子结点,继续向上查找
            parent = haffTree[child].parent; // 找到双亲结点
        }
        
        // 把逆序编码顺序存入haffCode
        for (int j = cd.length - 1; j >= 0; j--){
            haffCode[i].bit[cd.length-1 - j] = cd.bit[j];
        }
        
        // 保存haffTree的信息到haffCode
        haffCode[i].length = cd.length; // 保存好haffCode的长度
        haffCode[i].weight = cd.weight; // 保存编码对应的权值
    }
}

2.4 测试

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, 哈夫曼编码!\n");
    int i, j, n = 4, m = 0;
    
    // 权值
    int weight[] = {2,4,5,7};
    
    // 初始化哈夫曼树, 哈夫曼编码
    HaffNode *myHaffTree = malloc(sizeof(HaffNode) * (2*n-1));
    Code *myHaffCode = malloc(sizeof(Code) * n);
    
    // 当前n > MaxN,表示超界. 无法处理.
    if (n > MaxN) {
        printf("定义的n越界,修改MaxN!");
        exit(0);
    }
    
    // 1.构建哈夫曼树
    Haffman(weight, n, myHaffTree);
    // 2.根据哈夫曼树得到哈夫曼编码
    HaffmanCode(myHaffTree, n, myHaffCode);
    // 3.遍历输出
    for (i = 0; i < n; i++) {
        printf("Weight = %d\n",myHaffCode[i].weight);
        for (j = 0; j < myHaffCode[i].length; j++) // 打印编码
            printf("%d", myHaffCode[i].bit[j]);
        m = m + myHaffCode[i].weight * myHaffCode[i].length; // 累计WPL
        printf("\n");
    }
    printf("Huffman's WPL is:%d\n",m);

    return 0;
}
// 输出
Hello, 哈夫曼编码!
Weight = 2
110
Weight = 4
111
Weight = 5
10
Weight = 7
0
Huffman's WPL is:35

相关文章

  • 数据结构与算法-哈弗曼编码

    1. 概念 哈夫曼树,即最优二叉树,带权路径长度最小的二叉树,经常应用于数据压缩。 在计算机信息处理中,“哈夫曼编...

  • 哈弗曼编码

    基本思想 以字符的使用频率作为权构建一颗哈弗曼树,然后利用哈弗曼树对字符进行编码。 构造一颗哈弗曼树,是将要编码的...

  • Huffman树及Huffman编码

    Huffman树及Huffman编码 一.实验目的 掌握哈夫曼树的构造算法、哈夫曼编码原理。 二.实验要求与内容 ...

  • 【恋上数据结构与算法一】(十六)哈夫曼树

    哈夫曼编码(Huffman Coding) ◼ 哈夫曼编码,又称为霍夫曼编码,它是现代压缩算法的基础 ◼ 假设要把...

  • 哈弗曼编码

    代码: 测试文件: 原始文件,q1.txt: 根据q1编码结果,写出对and的编码,q2.txt: 运行结果: 有...

  • 基于哈夫曼算法的压缩解压缩程序--python实现

    一.实现效果 【压缩】 【解压缩】 【压缩效率】 二.哈夫曼算法 哈夫曼又称霍夫曼编码,是一种编码方式,哈夫曼编码...

  • 数据结构-哈夫曼树

    哈夫曼编码(Huffman Coding) ◼ 哈夫曼编码,又称为霍夫曼编码,它是现代压缩算法的基础◼ 假设要把字...

  • 19-哈夫曼树

    哈夫曼编码(Huffman Coding) 哈夫曼编码,又称霍夫曼编码,它是现代压缩算法的基础。 假如我们现在有这...

  • 二十五、哈夫曼树

    哈夫曼编码(Huffman Coding) 哈夫曼编码,又称为霍夫曼编码,它是现代压缩算法的基础 假设要把字符串【...

  • 18_哈夫曼树

    哈夫曼编码的用途 哈夫曼编码,又称霍夫曼编码,它是现代压缩算法的基础 假设要把字符串【ABBBBCCCCCCCCD...

网友评论

      本文标题:数据结构与算法-哈弗曼编码

      本文链接:https://www.haomeiwen.com/subject/obsiwhtx.html