问题:
输入:字符集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
。
- 首先,创建一个新的
QueueNode
结点newNode
,并为其分配内存空间。 - 将
node
赋值给newNode
的data
,并将newNode
的next
指针设置为NULL
。 - 检查优先队列是否为空,或者新节点的频率是否小于优先队列中第一个节点的频率。如果是,则将新节点插入到队列的开头,并返回新的队列头节点。
- 如果不是上述情况,通过一个循环找到合适的位置将新节点插入到队列中,保持队列按照频率从小到大排序。
- 最后,将新节点插入到找到的位置,并返回队列的头节点。
这段代码的作用是确保优先队列中的节点按照频率从小到大排序,以便后续构建哈夫曼树时能够选择频率最小的节点。
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
。
- 首先,检查传入的
root
结点是否为空,如果为空则直接返回,表示已经到达树的底部。 - 接着,检查当前结点是否为叶子结点(即没有左右子结点),如果是,则将当前路径上的编码保存到该叶子结点的
code
字段中,并打印出该叶子结点对应的字符和编码。 - 如果当前结点不是叶子结点,分别处理左子树和右子树:
- 对于左子树,将当前路径上的编码设置为
'0'
,然后递归调用generateHuffmanCode
函数处理左子结点。 - 对于右子树,将当前路径上的编码设置为
'1'
,然后递归调用generateHuffmanCode
函数处理右子结点。
- 对于左子树,将当前路径上的编码设置为
通过不断递归向下处理左右子树,并在叶子结点处保存编码,最终可以生成哈夫曼树中每个字符对应的哈夫曼编码。这个过程是基于哈夫曼树的特点:左子树路径标记为 '0'
,右子树路径标记为 '1'
,从根结点到叶子结点的路径即为该字符的哈夫曼编码。
在整个过程中,index
参数用来记录当前路径上的位置,code
数组用来保存当前路径上的编码。最终生成的哈夫曼编码保存在每个叶子结点的 code
字段中。