哈夫曼编码原理分析及代码实现(有注释)

哈夫曼编码

借鉴《趣学算法》–陈小玉
应用: 数据压缩
核心思想: 权值越大的叶子离根越近。
实现方法: 构建哈夫曼树:每次从数的集合中取出没有双亲且权值最小的两棵树作为左右子树(贪心的思想),构建一棵新树,新树的根节点的权值为其左右孩子结点权值之和,将新数插入到数的集合中,通过n-1次这样的合并,构建成的树即为哈夫曼树。(因为n个点,所以要进行n-1次合并);求哈夫曼编码:约定左分支上的编码为0,右分支上的编码为1,从叶子结点到根节点逆向求出每个字符的哈夫曼编码,从根节点到叶子结点路径上的字符组成的字符串为该叶子结点的哈夫曼编码。
具体步骤:
(1):数据结构要求:

  • 因为哈夫曼树中的节点都是两两合并的,所以不存在度为1的结点,则一棵有n个叶子结点的哈夫曼树共有2n-1个结点(n个结点合并需要n-1次合并,每次合并产生n-1个结点,因此结点总数为2 * n - 1)
  • 构建哈夫曼树后,为求每个字符的哈夫曼编码,需从叶子结点出发走一条从叶子到根的路径。
  • 译码(求每个叶子结点从根结点到叶子结点路径上的字符组成的字符串即哈夫曼编码)需要从根出发走一条从根到叶子的路径,那么我们需要知道每个结点的权值、双亲、左孩子、右孩子、和结点信息。

(2):初始化:
构造n结点为n个字符的单结点树(即只有一个树根)集合T = {t1, t2, t3, …, tn},每棵树只有一个带权的根结点,权值为该字符的使用频率。
(3)如果集合T中只剩下一颗树,则哈夫曼编码树构造成功,调到步骤(6)。否则,从集合T中取出没有双亲且权值最小的两棵树ti和tj,将他们合并成一颗新树zk,新树的做孩子为ti,右孩子为tj,zk的权值为ti和tj的权值和。
(4)从集合T中删去ti,tj,加入zk。
(5)重复(3) ~ (5)过程。
(6)约定左分支上的编码为0,右分支上的编码为1,从叶子结点到根节点逆向求出每个字符的哈夫曼编码,从根节点到叶子结点路径上的字符组成的字符串为该叶子结点的哈夫曼编码。
具体图解过程: 推荐看看陈小玉的趣学算法里哈夫曼的图解,很详细。
复杂度分析:
时间复杂度:O(n^2)
每一次合并时候找最大值和次小值的时间复杂度花费是O(n^2),编码和输出编码的时间复杂度花费是O( n ^ 2),对于整个算法的时间复杂度主要花费在这两个地方,所以时间复杂度为O(n^2)
空间复杂度:O(n * MAXBIT)
所需要的存储空间为结点结构体数组与编码结构体数组,哈夫曼数组HuffNode[]中的结点个数为n - 1个,每个包含bit[MAXBIT]和start两个域,所以该算法空间复杂度为O(n * MAXBIT)
代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

#define MAXBIT 100
#define MAXVALUE 10000
#define MAXLEAF 30
#define MAXNODE MAXLEAF * 2 - 1

typedef struct{
    double w;  //权重
    int pa; //父节点
    int lch; //左孩子节点
    int rch; //右孩子节点
    char val; //字符
}HNodeType;     //结点结构体

typedef struct {
    int bit[MAXBIT];
    int start;
}HCodeType;  //编码结构体

HNodeType HuffNode[MAXNODE];    //定义一个结点结构体
HCodeType HuffCode[MAXLEAF];    //定义一个编码结构体

//构建哈夫曼树
void HuffmanTree(HNodeType HuffNode[], int n){
    int x1, x2;   //构建哈夫曼树不同过程中两个最小权值结点的序号
    double m1, m2;  //构建哈夫曼树不同过程中两个最小权值结点的权值
    int i, j;   //循环变量
    //初始化哈夫曼数组HuffNode[]中的结点
    for(i = 0; i < 2 * n - 1; i ++) //这里多开n - 1个结点:存放新合并在结点
    {
        HuffNode[i].w = 0;  //初始权重为0
        //初始父亲结点 、左右儿子结点都为-1 表示不存在
        HuffNode[i].pa = -1;
        HuffNode[i].lch = -1;
        HuffNode[i].rch = -1;
    }
    
    //输入n个叶子结点的权重
    for(i = 0; i < n; i ++){
        cout<<"please input value and weight of leaf node "<<i + 1<<'\n';
        cin>>HuffNode[i].val>>HuffNode[i].w;
    }
    
    //构建哈夫曼树
    for(i = 0; i < n - 1; i ++)//n个树两两合并,需要n - 1 次
    {
        //找无父节点中需要合并数中最小权重的两个数, 然后将他们合并成一颗树
        m1 = m2 = MAXVALUE; //两个最小权重数的值默认最大, m1:最小值,m2:次小值
        x1 = x2 = -1;   //两个最小权重数的值的序号为 - 1, x1:最小值序号 x2:次小值编号
        
        //找最小值和次小值
        for(j = 0; j < n + i; j ++){
            if(HuffNode[j].w < m1 && HuffNode[j].pa == -1){
                m2 = m1;
                x2 = x1;
                m1 = HuffNode[j].w;
                x1 = j;
            }
            else if(HuffNode[j].w < m2 && HuffNode[j].pa == -1){
                m2 = HuffNode[j].w;
                x2 = j;
            }
        }
        
        //更新合成该树的两个子节点以及父结点信息
        HuffNode[x1].pa = n + i;    //子结点的父结点指向新构成的新结点
        HuffNode[x2].pa = n + i;
        HuffNode[n + i].w = m1 + m2;    //新构成结点的权值是两个子结点的权值和
        HuffNode[n + i].lch = x1;   //新构成的左右孩子结点赋值(左右顺序没有关系)
        HuffNode[n + i].rch = x2;   
        
        cout<<"x1.weight and x2.weight in round "<<i + 1<< '\t'<<HuffNode[x1].w<<'\t'<<HuffNode[x2].w<<'\n';
    }
}

//构建哈夫曼编码
void HuffmanCode(HCodeType HuffCode[], int n){
    HCodeType cd;   //定义一个临时变量来存放求解编码的信息
    int c, p, i, j;
    for(i = 0; i < n; i ++) //遍历叶子结点,通过叶子结点到根结点逆向求出每个字符的哈夫曼编码
    {
        cd.start = n - 1;   //从最后向前更新编码值(因为我们构建编码的时候是从叶子到根的顺序,正好与编码顺序相反)
        c = i;  //c记录当前结点的编号
        p = HuffNode[c].pa; //p记录当前结点的父亲结点
        while(p != -1){
            //约定左分支上编码为0,右分支上编码为1
            if(HuffNode[p].lch == c)cd.bit[cd.start] = 0;
            else cd.bit[cd.start] = 1;
            cd.start --;//cd向前移动一位
            c = p;  //c更新成c的父结点编号(即模拟一个从叶子结点向根结点走的过程)
            p = HuffNode[c].pa; //p也更新成c(此时的c是上次c的父结点)的父亲结点
        }
        //将构成的哈夫曼编码存入该叶子结点对应的领接表中
        for(j = cd.start + 1; j < n; j ++){
            HuffCode[i].bit[j] = cd.bit[j];
            HuffCode[i].start = cd.start;
        }
    }
}
int main()
{
    int n;
    cout<<"please input n: "<<'\n';
    cin>>n;
    HuffmanTree(HuffNode, n);
    HuffmanCode(HuffCode, n);
    
    for(int i = 0; i < n; i ++){
        cout<<HuffNode[i].val<<":Huffman code is: ";
        //这里start指向相对于编码的首尾要前一位,所以要 + 1
        for(int j = HuffCode[i].start + 1; j < n; j ++)cout<<HuffCode[i].bit[j];    
        cout<<'\n';
    }
    
    return 0;
}

运行结果:
请添加图片描述

  • 9
    点赞
  • 100
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
以下是用C语言创建哈编码表的代码注释: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_TREE_HT 100 // 定义哈树最大高度 // 哈树节点结构体 struct MinHeapNode { char data; // 该节点对应的字符 unsigned freq; // 字符出现的频率 struct MinHeapNode *left, *right; // 左子节点和右子节点 }; // 哈树结构体 struct MinHeap { unsigned size; // 最小堆中节点数量 unsigned capacity; // 最小堆的容量 struct MinHeapNode **array; // 指向哈树节点的指针数组 }; // 创建一个新的哈树节点 struct MinHeapNode* newNode(char data, unsigned freq) { struct MinHeapNode* node = (struct MinHeapNode*)malloc(sizeof(struct MinHeapNode)); node->left = node->right = NULL; node->data = data; node->freq = freq; return node; } // 创建一个容量为capacity的空的最小堆 struct MinHeap* createMinHeap(unsigned capacity) { struct MinHeap* minHeap = (struct MinHeap*)malloc(sizeof(struct MinHeap)); minHeap->size = 0; minHeap->capacity = capacity; minHeap->array = (struct MinHeapNode**)malloc(minHeap->capacity * sizeof(struct MinHeapNode*)); return minHeap; } // 交换两个节点 void swapMinHeapNode(struct MinHeapNode** a, struct MinHeapNode** b) { struct MinHeapNode* t = *a; *a = *b; *b = t; } // 最小堆的堆化过程 void minHeapify(struct MinHeap* minHeap, int idx) { int smallest = idx; int left = 2 * idx + 1; int right = 2 * idx + 2; if (left < minHeap->size && minHeap->array[left]->freq < minHeap->array[smallest]->freq) smallest = left; if (right < minHeap->size && minHeap->array[right]->freq < minHeap->array[smallest]->freq) smallest = right; if (smallest != idx) { swapMinHeapNode(&minHeap->array[smallest], &minHeap->array[idx]); minHeapify(minHeap, smallest); } } // 判断堆是否只有一个节点 int isSizeOne(struct MinHeap* minHeap) { return (minHeap->size == 1); } // 弹出最小频率的节点 struct MinHeapNode* extractMin(struct MinHeap* minHeap) { struct MinHeapNode* temp = minHeap->array[0]; minHeap->array[0] = minHeap->array[minHeap->size - 1]; --minHeap->size; minHeapify(minHeap, 0); return temp; } // 插入新节点 void insertMinHeap(struct MinHeap* minHeap, struct MinHeapNode* minHeapNode) { ++minHeap->size; int i = minHeap->size - 1; while (i && minHeapNode->freq < minHeap->array[(i - 1) / 2]->freq) { minHeap->array[i] = minHeap->array[(i - 1) / 2]; i = (i - 1) / 2; } minHeap->array[i] = minHeapNode; } // 判断是否为叶子节点 int isLeaf(struct MinHeapNode* root) { return !(root->left) && !(root->right); } // 创建哈树 struct MinHeapNode* buildHuffmanTree(char data[], int freq[], int size) { struct MinHeapNode *left, *right, *top; struct MinHeap* minHeap = createMinHeap(size); for (int i = 0; i < size; ++i) minHeap->array[i] = newNode(data[i], freq[i]); minHeap->size = size; while (!isSizeOne(minHeap)) { left = extractMin(minHeap); right = extractMin(minHeap); top = newNode('$', left->freq + right->freq); top->left = left; top->right = right; insertMinHeap(minHeap, top); } return extractMin(minHeap); } // 打印哈编码 void printCodes(struct MinHeapNode* root, int arr[], int top) { if (root->left) { arr[top] = 0; printCodes(root->left, arr, top + 1); } if (root->right) { arr[top] = 1; printCodes(root->right, arr, top + 1); } if (isLeaf(root)) { printf("%c: ", root->data); for (int i = 0; i < top; ++i) printf("%d", arr[i]); printf("\n"); } } // 创建哈编码表 void HuffmanCodes(char data[], int freq[], int size) { struct MinHeapNode* root = buildHuffmanTree(data, freq, size); int arr[MAX_TREE_HT], top = 0; printCodes(root, arr, top); } // 测试代码 int main() { char data[] = { 'a', 'b', 'c', 'd', 'e', 'f' }; int freq[] = { 5, 9, 12, 13, 16, 45 }; int size = sizeof(data) / sizeof(data[0]); HuffmanCodes(data, freq, size); return 0; } ``` 代码注释: 1. 宏定义`MAX_TREE_HT`表示哈树的最大高度。 2. 定义哈树节点结构体`MinHeapNode`,包含字符、频率、左子节点和右子节点。 3. 定义哈树结构体`MinHeap`,包含最小堆中节点数量、容量和指向哈树节点的指针数组。 4. `newNode`函数创建一个新的哈树节点。 5. `createMinHeap`函数创建一个容量为capacity的空的最小堆。 6. `swapMinHeapNode`函数交换两个节点。 7. `minHeapify`函数是最小堆的堆化过程。 8. `isSizeOne`函数判断堆是否只有一个节点。 9. `extractMin`函数弹出最小频率的节点。 10. `insertMinHeap`函数插入新节点。 11. `isLeaf`函数判断是否为叶子节点。 12. `buildHuffmanTree`函数创建哈树。 13. `printCodes`函数打印哈编码。 14. `HuffmanCodes`函数创建哈编码表。 15. `main`函数测试代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值