【数据结构】哈夫曼树 哈夫曼编码 解析+完整代码

6.1 哈夫曼树

  • 带权路径长度的概念

    • 结点的权:

      有某种现实含义的数值(如:表示结点的重要性等)

    • 结点的带权路径长度:

      从树的根到该结点的路径长度(经过的边数)与该结点上权值的乘积。

      结点的带权路径长度 = 从根到该节点的路径长度 × 该结点上权值的乘积 结点的带权路径长度=从根到该节点的路径长度\times该结点上权值的乘积 结点的带权路径长度=从根到该节点的路径长度×该结点上权值的乘积

    • 树的带权路径长度:

      树中所有叶结点的带权路径长度之和。如:

  • 哈夫曼树定义

    给定n个带权叶结点,要构造出二叉树,其中带权路径长度(WPL)最小的二叉树称为哈夫曼树,也称为最优二叉树。

  • 哈夫曼树的构造

    给定n个权值分别为 w 1 , w 2 , . . . , w n w_1,w_2,...,w_n w1,w2,...,wn的结点,构造哈夫曼树的算法描述如下:

    (1)将这n个结点分别作为n棵仅含一个结点的二叉树,构成森林F;

    (2)构造一个新结点,从F中选取两棵根结点权值最小的树作为新结点的左右子树,并且将新结点的权值置为左右子树上根结点的权值之和;

    (3)从F中删除刚才选出的两棵树,同时将新得到的树加入F中。

    (4)重复第2和第3步,直至F中只剩下一棵树为止。

    image-20240426091821669
  • 哈夫曼树的性质

    1.每个初始结点最终都会成为叶结点,且权值越小的结点到根结点的路径长度越大;

    2.哈夫曼树的结点总数为2n-1;

    3.哈夫曼树中不存在度为1的结点;

    4.哈夫曼树不唯一,但WPL必然相同且最优。

  • 哈夫曼编码的定义

    • 固定长度编码:每个字符用相等长度的二进制位表示。
    • 可变长度编码:允许对不同字符用不等长的二进制位表示。
    • 前缀编码:没有一个编码是另一个编码的前缀。
    • 由哈夫曼树得到哈夫曼编码——将字符频次作为字符结点权值,构造哈夫曼树,即可得哈夫曼编码,可用于数据压缩。

*完整代码 哈夫曼树

#include <stdio.h>
#include <stdlib.h>

// 定义哈夫曼树节点结构
typedef struct HuffmanNode {
    char data;              // 字符
    int freq;               // 频率
    struct HuffmanNode *left, *right;   // 左右子节点
} HuffmanNode;

// 定义优先级队列结构
typedef struct PriorityQueue {
    int size;               // 队列当前大小
    int capacity;           // 队列容量
    HuffmanNode **array;    // 存储哈夫曼树节点的数组指针
} PriorityQueue;

// 创建哈夫曼树节点
HuffmanNode* createNode(char data, int freq) {
    HuffmanNode* node = (HuffmanNode*)malloc(sizeof(HuffmanNode));  // 分配内存空间
    node->data = data;  // 设置节点字符
    node->freq = freq;  // 设置节点频率
    node->left = node->right = NULL;   // 初始化左右子节点为空
    return node;  // 返回节点指针
}

// 创建优先级队列
PriorityQueue* createPriorityQueue(int capacity) {
    PriorityQueue* queue = (PriorityQueue*)malloc(sizeof(PriorityQueue));  // 分配内存空间
    queue->size = 0;  // 初始化队列大小为0
    queue->capacity = capacity;  // 设置队列容量
    queue->array = (HuffmanNode**)malloc(queue->capacity * sizeof(HuffmanNode*));  // 分配内存空间
    return queue;  // 返回队列指针
}

// 交换两个节点
void swapNodes(HuffmanNode** a, HuffmanNode** b) {
    HuffmanNode* temp = *a;
    *a = *b;
    *b = temp;
}

// 向下堆化
void minHeapify(PriorityQueue* queue, int idx) {
    int smallest = idx;
    int left = 2 * idx + 1;  // 计算左子节点索引
    int right = 2 * idx + 2;  // 计算右子节点索引

    // 找出三个节点中最小的节点
    if (left < queue->size && queue->array[left]->freq < queue->array[smallest]->freq) {
        smallest = left;
    }
    if (right < queue->size && queue->array[right]->freq < queue->array[smallest]->freq) {
        smallest = right;
    }

    // 如果最小节点不是当前节点,交换节点并递归向下堆化
    if (smallest != idx) {
        swapNodes(&queue->array[idx], &queue->array[smallest]);
        minHeapify(queue, smallest);
    }
}

// 插入节点
void insertNode(PriorityQueue* queue, HuffmanNode* node) {
    queue->size++;  // 队列大小加1
    int i = queue->size - 1;  // 获取最后一个位置的索引

    queue->array[i] = node;  // 将节点插入最后一个位置

    // 如果插入节点的频率小于父节点的频率,向上调整
    while (i && queue->array[i]->freq < queue->array[(i - 1) / 2]->freq) {
        swapNodes(&queue->array[i], &queue->array[(i - 1) / 2]);
        i = (i - 1) / 2;
    }
}

// 提取最小节点
HuffmanNode* extractMin(PriorityQueue* queue) {
    if (queue->size == 0) return NULL;  // 如果队列为空,返回空指针

    HuffmanNode* root = queue->array[0];  // 获取根节点
    queue->array[0] = queue->array[queue->size - 1];  // 将最后一个节点移到根节点位置
    queue->size--;  // 队列大小减1

    minHeapify(queue, 0);  // 向下堆化

    return root;  // 返回根节点
}

// 构建哈夫曼树
HuffmanNode* buildHuffmanTree(char data[], int freq[], int size) {
    PriorityQueue* queue = createPriorityQueue(size);  // 创建优先级队列

    // 将字符和频率构建成哈夫曼树节点,并插入优先级队列中
    for (int i = 0; i < size; i++) {
        insertNode(queue, createNode(data[i], freq[i]));
    }

    // 从优先级队列中不断取出最小的两个节点,构建哈夫曼树,直到队列中只剩一个节点
    while (queue->size != 1) {
        HuffmanNode* left = extractMin(queue);
        HuffmanNode* right = extractMin(queue);

        // 创建新节点作为父节点,频率为左右子节点频率之和
        HuffmanNode* top = createNode('\0', left->freq + right->freq);
        top->left = left;
        top->right = right;

        // 插入新节点到队列中
        insertNode(queue, top);
    }

    // 返回根节点
    return extractMin(queue);
}

// 打印哈夫曼编码
void printHuffmanCodes(HuffmanNode* root, int arr[], int top) {
    // 遍历树,生成编码
    if (root->left) {
        arr[top] = 0;
        printHuffmanCodes(root->left, arr, top + 1);
    }

    if (root->right) {
        arr[top] = 1;
        printHuffmanCodes(root->right, arr, top + 1);
    }

    // 当遍历到叶子节点时,打印字符及其编码
    if (!root->left && !root->right) {
        printf("%c: ", root->data);
        for (int i = 0; i < top; i++) {
            printf("%d", arr[i]);
        }
        printf("\n");
    }
}

// 主函数
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]);       // 字符集合大小

    // 构建哈夫曼树
    HuffmanNode* root = buildHuffmanTree(data, freq, size);

    int arr[100];  // 存储编码的数组
    int top = 0;   // 记录编码
    // 打印哈夫曼编码
    printf("哈夫曼编码:\n");
    printHuffmanCodes(root, arr, top);

    return 0;
}
  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值