哈夫曼编码C语言实现(数据结构,算法设计与分析贪心算法)

问题: 

输入:字符集C,每个字符出现的频率f

输出:每个字符的哈夫曼编码

思路:

采用贪心法,贪心策略是按字符出现频率由小到大排序存到优先队列中,正确性证明略。

用到的数据结构有链式队列,二叉树。

代码:

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

// 定义哈夫曼树的节点结构
typedef struct Node {
    char data;
    int freq;
    char* code;//哈夫曼编码数组 
    struct Node* left;
    struct Node* right;
} Node;

// 定义优先队列的节点结构
typedef struct QueueNode {
    Node* data;//指向哈夫曼树的节点的指针 
    struct QueueNode* next;
} QueueNode;

// 初始化一个空的优先队列
QueueNode* initQueue() {
    return NULL;
}

// 向优先队列中插入一个节点
QueueNode* insertQueue(QueueNode* head, Node* node) {
    QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
    newNode->data = node;
    newNode->next = NULL;

    if (head == NULL || node->freq < head->data->freq) {
        newNode->next = head;
        return newNode;//如果当前节点频率最小作为新的头节点并返回新的头节点 
    }

    QueueNode* current = head;
    while (current->next != NULL && current->next->data->freq < node->freq) {
        current = current->next;
    }

    newNode->next = current->next;
    current->next = newNode;

    return head;//否则返回旧的 
}

// 从优先队列中取出频率最小的节点
Node* popQueue(QueueNode** head) {//指向头节点的指针的指针 
    if (*head == NULL) {
        return NULL;
    }

    Node* node = (*head)->data;
    QueueNode* temp = *head;
    *head = (*head)->next;
    free(temp);

    return node;
}

// 构建哈夫曼树
Node* buildHuffmanTree(char* C, int* f, int n) {
    QueueNode* queue = initQueue();

    for (int i = 0; i < n; i++) {
        Node* node = (Node*)malloc(sizeof(Node));
        node->data = C[i];
        node->freq = f[i];
        node->code = NULL;
        node->left = NULL;
        node->right = NULL;

        queue = insertQueue(queue, node);//把哈夫曼树节点放到优先队列中存储 
    }

    while (queue->next != NULL) {
        Node* left = popQueue(&queue);
        Node* right = popQueue(&queue);

        Node* parent = (Node*)malloc(sizeof(Node));
        parent->data = '\0';
        parent->freq = left->freq + right->freq;
        parent->code = NULL;
        parent->left = left;
        parent->right = right;

        queue = insertQueue(queue, parent);
    }

    return popQueue(&queue);//返回从优先队列中弹出的节点
}

// 递归遍历哈夫曼树,生成每个字符的哈夫曼编码
void generateHuffmanCode(Node* root, char* code, int index) {
    if (root == NULL) {
        return;
    }

    if (root->left == NULL && root->right == NULL) {
        code[index] = '\0';
        root->code = strdup(code);
        printf("Character %c: %s\n", root->data, root->code);
        return;
    }

    if (root->left != NULL) {
        code[index] = '0';
        generateHuffmanCode(root->left, code, index + 1);
    }

    if (root->right != NULL) {
        code[index] = '1';
        generateHuffmanCode(root->right, code, index + 1);
    }
}

// 主函数
int main() {
    char C[] = {'a', 'b', 'c', 'd', 'e'};
    int f[] = {5, 9, 12, 13, 16};
    int n = sizeof(C) / sizeof(C[0]);

    Node* root = buildHuffmanTree(C, f, n);

    char code[100];
    generateHuffmanCode(root, code, 0);

    return 0;
}

运行结果: 

 

难点解释: 

1.insertQueue函数 

QueueNode* insertQueue(QueueNode* head, Node* node) {
    QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
    newNode->data = node;
    newNode->next = NULL;

    if (head == NULL || node->freq < head->data->freq) {
        newNode->next = head;
        return newNode;
    }

    QueueNode* current = head;
    while (current->next != NULL && current->next->data->freq < node->freq) {
        current = current->next;
    }

    newNode->next = current->next;
    current->next = newNode;

    return head;
}

 

这段代码是用来向优先队列中插入一个节点的函数 insertQueue

  1. 首先,创建一个新的 QueueNode 结点 newNode,并为其分配内存空间。
  2. 将 node 赋值给 newNode 的 data,并将 newNode 的 next 指针设置为 NULL
  3. 检查优先队列是否为空,或者新节点的频率是否小于优先队列中第一个节点的频率。如果是,则将新节点插入到队列的开头,并返回新的队列头节点。
  4. 如果不是上述情况,通过一个循环找到合适的位置将新节点插入到队列中,保持队列按照频率从小到大排序。
  5. 最后,将新节点插入到找到的位置,并返回队列的头节点。

这段代码的作用是确保优先队列中的节点按照频率从小到大排序,以便后续构建哈夫曼树时能够选择频率最小的节点。

2. buildHuffmanTree函数

Node* buildHuffmanTree(char* C, int* f, int n) {
    QueueNode* queue = initQueue();

    for (int i = 0; i < n; i++) {
        Node* node = (Node*)malloc(sizeof(Node));
        node->data = C[i];
        node->freq = f[i];
        node->code = NULL;
        node->left = NULL;
        node->right = NULL;

        queue = insertQueue(queue, node);//把哈夫曼树节点放到优先队列中存储 
    }

    while (queue->next != NULL) {
        Node* left = popQueue(&queue);
        Node* right = popQueue(&queue);

        Node* parent = (Node*)malloc(sizeof(Node));
        parent->data = '\0';
        parent->freq = left->freq + right->freq;
        parent->code = NULL;
        parent->left = left;
        parent->right = right;

        queue = insertQueue(queue, parent);
    }

    return popQueue(&queue);//返回从优先队列中弹出的节点
}

 在这段代码中,return popQueue(&queue); 的作用是返回从优先队列中弹出的节点,而不是返回整个队列。这是因为在哈夫曼树的构建过程中,我们通常需要从优先队列中弹出频率最小的节点,然后将其用于构建哈夫曼树。

3.generateHuffmanCode函数

void generateHuffmanCode(Node* root, char* code, int index) {
    if (root == NULL) {
        return;
    }

    if (root->left == NULL && root->right == NULL) {
        code[index] = '\0';
        root->code = strdup(code);
        printf("Character %c: %s\n", root->data, root->code);
        return;
    }

    if (root->left != NULL) {
        code[index] = '0';
        generateHuffmanCode(root->left, code, index + 1);
    }

    if (root->right != NULL) {
        code[index] = '1';
        generateHuffmanCode(root->right, code, index + 1);
    }
}

这段代码是用来生成哈夫曼编码的函数 generateHuffmanCode

  1. 首先,检查传入的 root 结点是否为空,如果为空则直接返回,表示已经到达树的底部。
  2. 接着,检查当前结点是否为叶子结点(即没有左右子结点),如果是,则将当前路径上的编码保存到该叶子结点的 code 字段中,并打印出该叶子结点对应的字符和编码。
  3. 如果当前结点不是叶子结点,分别处理左子树和右子树:
    • 对于左子树,将当前路径上的编码设置为 '0',然后递归调用 generateHuffmanCode 函数处理左子结点。
    • 对于右子树,将当前路径上的编码设置为 '1',然后递归调用 generateHuffmanCode 函数处理右子结点。

通过不断递归向下处理左右子树,并在叶子结点处保存编码,最终可以生成哈夫曼树中每个字符对应的哈夫曼编码。这个过程是基于哈夫曼树的特点:左子树路径标记为 '0',右子树路径标记为 '1',从根结点到叶子结点的路径即为该字符的哈夫曼编码。

在整个过程中,index 参数用来记录当前路径上的位置,code 数组用来保存当前路径上的编码。最终生成的哈夫曼编码保存在每个叶子结点的 code 字段中。

  • 13
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值