哈夫曼树又称为最优树,作用是找到一种效率最高的判断树。
路径:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径。
结点的路径长度:两结点间路径上的分支树
结点的权。
权值越大的结点离根节点越近,权值越小的结点离根节点越远,能使总路径越短。
贪心算法
例如:
有 4 个结点分别是 a b c d 权值分别为 7 5 2 4,试构造哈夫曼树。
因为构造哈夫曼树的时候是,选用两小造新树。
总结
typedef struct
{
int weight;//结点的权重
int parent;//结点的双亲
int lchild,rchild;//结点的左、右孩子下标
}HuffmanTree;//动态分配数组存储哈夫曼树
假设有如下n个权值:7、5、2、4、8、3。
#include
#include
#include
/*这个是用了第0下标的代码*/
#define MaxSize 1000 //定义最大结点数
//结点结构体
typedef struct Node{
int weight ; //权值
int parent ; //父节点下标
int left_child ; //左子节点下标
int right_child; //右子结点下标
}Node;
//哈夫曼树结构体
typedef struct HuffmanTree{
int n; //节点数
Node nodes[MaxSize]; //结点数组
}HuffmanTree;
//选择两个权值最小的结点
void select_min_two(HuffmanTree *ht,int *p1, int *p2)
{
int i;
int w1=INT_MAX,w2=INT_MAX; //初始化为最大值
*p1=*p2=-1; //初始化为无效值
for (i = 0; i< ht->n; i++)
{ // 如果当前节点是没有父节点的,且它的权重比当前最小值还要小
if (ht->nodes[i].parent == -1) //未被选择
{
if(ht->nodes[i].weightnodes[i].weight; // 更新最小值
}
else if(ht->nodes[i].weightnodes[i].weight; // 更新次小值
}
}
}
}
//构造哈夫曼树
void build_huffman_tree(HuffmanTree *ht,int *weights,int n)
{
int i,p1,p2;
if(n>MaxSize) // 节点数不能超过最大值
{
printf("Too many nodes");
return ;
}
if (n == 1) { // 如果只有一个节点,构建一个单节点树
ht->n = 1;
ht->nodes[0].weight = weights[0];
ht->nodes[0].parent = -1;
ht->nodes[0].left_child = -1;
ht->nodes[0].right_child = -1;
return;
}
ht->n=n; // 节点数等于叶子节点数
// 初始化叶子节点
for (i = 0; i < n; i++) {
ht->nodes[i].weight = weights[i];
ht->nodes[i].parent = -1;
ht->nodes[i].left_child = -1;
ht->nodes[i].right_child = -1;
}
// 初始化非叶子节点
for (i = n; i < 2 * n - 1; i++) {
ht->nodes[i].weight = 0;
ht->nodes[i].parent = -1;
ht->nodes[i].left_child = -1;
ht->nodes[i].right_child = -1;
}
// 逐步合并节点,构造哈夫曼树
for (i = n; i < 2 * n - 1; i++) {
select_min_two(ht, &p1, &p2); // 选择权值最小的两个节点
ht->nodes[i].weight = ht->nodes[p1].weight + ht->nodes[p2].weight;
ht->nodes[p1].parent = i;
ht->nodes[p2].parent = i;
ht->nodes[i].left_child = p1;
ht->nodes[i].right_child = p2;
}
}
int main() {
int weights[] = {1, 2, 3, 4, 5};
int n = sizeof(weights) / sizeof(weights[0]);
HuffmanTree ht;
build_huffman_tree(&ht, weights, n);
return 0;
}
这是从下标1开始的代码
#include
#include
#define MAX_NODES 1000
// 哈夫曼树的结构体
typedef struct {
int weight; // 权值
int parent; // 父节点下标
int lchild; // 左子节点下标
int rchild; // 右子节点下标
} HuffmanTree;
void select_min_two(HuffmanTree *ht, int n, int *p1, int *p2) {
// 找到权值最小的两个节点
int min1 = MAX_NODES, min2 = MAX_NODES; // min1和min2分别表示最小和次小的权值
for (int i = 1; i <= n; i++) {
if (ht[i].weight < min1 && ht[i].parent == 0) {
min2 = min1;
*p2 = *p1;
min1 = ht[i].weight;
*p1 = i;
} else if (ht[i].weight < min2 && ht[i].parent == 0) {
min2 = ht[i].weight;
*p2 = i;
}
}
}
void build_huffman_tree(HuffmanTree *ht, int *weights, int n) {
// 初始化哈夫曼树
for (int i = 1; i <= n; i++) {
ht[i].weight = weights[i];
ht[i].parent = 0;
ht[i].lchild = 0;
ht[i].rchild = 0;
}
// 构造哈夫曼树
int m = 2 * n - 1;
for (int i = n + 1; i <= m; i++) {
int p1 = 0, p2 = 0;
select_min_two(ht, i - 1, &p1, &p2); // 找到权值最小的两个节点
ht[p1].parent = i;
ht[p2].parent = i;
ht[i].lchild = p1;
ht[i].rchild = p2;
ht[i].weight = ht[p1].weight + ht[p2].weight;
}
}
哈夫曼编码是一种可变长度编码的算法,用于将字符集中的字符映射成不同长度的二进制码。它是一种无损压缩算法,可以有效地压缩文本、音频、图像等数据。
哈夫曼编码的基本思想是,为频繁出现的字符分配较短的编码,为不频繁出现的字符分配较长的编码。这样做可以使得编码后的数据具有更高的压缩率。具体来说,哈夫曼编码的过程如下:
由于哈夫曼编码采用可变长度编码,使得频繁出现的字符用较短的编码表示,不频繁出现的字符用较长的编码表示,因此可以达到比固定长度编码更高的压缩率。
**例:**要传输的字符集 D = {C,A,S,T,; }(注意这个分号也是,并没有多打),每个字符出现的频率 W = {2,4,2,3,3}。
哈夫曼编码的两个问题
#include
#include
#include
// 哈夫曼树结构体定义
typedef struct Node {
char character; // 字符
int frequency; // 频率
struct Node *left, *right;
} Node;
// 函数声明
Node *createNode(char character, int frequency);
Node **createPriorityQueue(int size);
void insertPriorityQueue(Node **queue, Node *node, int size);
Node *removeSmallestPriorityQueue(Node **queue, int size);
Node *buildHuffmanTree(char *characters, int *frequencies, int size);
void printHuffmanCodes(Node *root, int *arr, int top);
int main() {
// 示例字符及频率
char characters[] = {'a', 'b', 'c', 'd', 'e', 'f'};
int frequencies[] = {5, 9, 12, 13, 16, 45};
int size = sizeof(characters) / sizeof(characters[0]);
// 创建哈夫曼树
Node *root = buildHuffmanTree(characters, frequencies, size);
// 输出哈夫曼编码
int arr[100], top = 0;
printf("Huffman Codes:\n");
printHuffmanCodes(root, arr, top);
return 0;
}
// 创建结点函数
Node *createNode(char character, int frequency) {
Node *node = (Node *)malloc(sizeof(Node));
node->character = character;
node->frequency = frequency;
node->left = node->right = NULL;
return node;
}
// 创建优先级队列(按频率升序)
Node **createPriorityQueue(int size) {
Node **queue = (Node **)malloc(size * sizeof(Node *));
for (int i = 0; i < size; i++) {
queue[i] = NULL;
}
return queue;
}
// 插入优先级队列
void insertPriorityQueue(Node **queue, Node *node, int size) {
int i;
for (i = 0; i < size; i++) {
if (queue[i] == NULL || queue[i]->frequency > node->frequency) {
break;
}
}
for (int j = size - 1; j > i; j--) {
queue[j] = queue[j - 1];
}
queue[i] = node;
}
// 移除优先级队列中最小元素
Node *removeSmallestPriorityQueue(Node **queue, int size) {
Node *smallest = queue[0];
for (int i = 0; i < size - 1; i++) {
queue[i] = queue[i + 1];
}
queue[size - 1] = NULL;
return smallest;
}
// 构建哈夫曼树
Node *buildHuffmanTree(char *characters, int *frequencies, int size) {
// 创建优先级队列
Node **queue = createPriorityQueue(size);
int i;
for (i = 0; i < size; ++i) {
queue[i] = createNode(characters[i], frequencies[i]);
}
// 构建哈夫曼树
while (i > 1) {
// 从优先级队列中移除两个最小频率的节点
Node *left = removeSmallestPriorityQueue(queue, i);
Node *right = removeSmallestPriorityQueue(queue, --i);
// 创建一个新节点,左右子节点分别是两个最小频率的节点
Node *internalNode = createNode('\0', left->frequency + right->frequency);
internalNode->left = left;
internalNode->right = right;
// 将新节点插入优先级队列
insertPriorityQueue(queue, internalNode, i);
--i;
}
// 返回哈夫曼树的根节点
Node *root = queue[0];
free(queue);
return root;
}
// 输出哈夫曼编码
void printHuffmanCodes(Node *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->character);
for (int i = 0; i < top; ++i) {
printf("%d", arr[i]);
}
printf("\n");
}
}
等长编码
哈夫曼编码
编码过程:
解码过程:
通过这个解码过程,我们成功地还原了原始文件的内容。这就是哈夫曼编码的无损解压缩过程。