【实验内容】
1、假设用于通信的电文仅由8个字母 {a, b, c, d, e, f, g, h} 构成,它们在电文中出现的概率分别为{ 0.07, 0.19, 0.02, 0.06, 0.32, 0.03, 0.21, 0.10 },试为这8个字母设计哈夫曼编码。
提示:包含两个过程:(1)构建哈夫曼树,(2)输出编码。
我的思路:
(1)建造哈夫曼树的过程直接按照哈夫曼思想模拟即可
(2)输出编码。而 哈夫曼编码函数,我使用了 图论中的 dfs回溯算法 的思想,采用以 递归为 核心的方法遍历整棵树,并将走过的左路径标记为 0,将走过的右路径标记为 1,到了叶子节点收集路径结果,
每一条路径我使用 一维数组 path 记录
全部路径的结果,我使用 二维数组 result 存储
最后打印 result 数组即可
简而言之:
用一维数组记录路径 上的 0 和 1,到了叶子节点就成为一条完整的编码
将每一条编码用 二维数组 存起来
头文件 Head.h
建造一棵哈夫曼树的相关函数和操作
#pragma once #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<stdlib.h> #include<stdbool.h> #define MAX 40 // 创建树节点 typedef struct HtNode { int ww; int parent, Lchild, Rchild; }HtNode; // 哈夫曼树结构 typedef struct HtTree { int root; HtNode ht[MAX]; }PHtTree; // 构造哈夫曼树 // m 为权值节点数量,w 为权值数组 PHtTree* CreateHuffMan(int m, int* w) { // 创建带有 m 个叶子节点的哈夫曼树 PHtTree* pht = (PHtTree*)malloc(sizeof(PHtTree)); if (pht == NULL) { perror("malloc fail !"); return NULL; } // 初始化 for (int i = 0; i < 2 * m - 1; ++i) { pht->ht[i].Lchild = pht->ht[i].Rchild = pht->ht[i].parent = -1; // 将数组前 m 个位置放入 权值数组元素 if (i < m) pht->ht[i].ww = w[i]; else pht->ht[i].ww = -1; } // 构建哈夫曼树 int i; for (i = 0; i < m - 1; i++) { // 两个m 找最小权值,两个 index 记录这两节点的下标 int m1, m2, index1, index2; m1 = m2 = INT_MAX; index1 = index2 = -1; // 每轮都在区间 [0, m + i] 找两个最小权值节点,且没有父节点(即没有使用过) for (int j = 0; j < m + i; ++j) { if (pht->ht[j].ww < m1 && pht->ht[j].parent == -1) { m2 = m1; index2 = index1; m1 = pht->ht[j].ww; index1 = j; } else if (pht->ht[j].ww < m2 && pht->ht[j].parent == -1) { m2 = pht->ht[j].ww; index2 = j; } } // 找到节点之后,构建父节点,并将父节点放进数组中 pht->ht[index1].parent = m + i; pht->ht[index2].parent = m + i; pht->ht[m + i].ww = m1 + m2; pht->ht[m + i].Lchild = index1; pht->ht[m + i].Rchild = index2; } pht->root = m + i; // 更新根节点:m+i 即为 m + (m-2) return pht; }
哈夫曼编码函数 HuffmanEncode
#include"Head.h" // 哈夫曼编码 // 思路:遍历每一条路,到叶子节点记录结果,左边标记为 0, 右边标记为 1 // p 遍历 path :从下标 1 开始存放编码串,下标 0 存放字符串长度(表示该编码串长度,方便遍历) // r 遍历 result:代表一一存放结果 int r = 0, p = 1; int result[MAX][MAX]; // 收集 path 的每一次结果 int path[MAX]; // 记录每一条路径的编码 void HuffmanEncode(HtNode* ht, int root) { if (root == -1) return; // 空节点返回 if (ht[root].Lchild == -1 && ht[root].Rchild == -1) { // 叶子节点 就记录结果 path[0] = p - 1; // p-1 代表此时 path数组中收集的 路径上的(一串 1 和 0)的数量:方便遍历,因为每一串编码不等长,所以需要记录数量 memcpy(result + r, path, sizeof(int)*p); // 将一维数组 path 存放进二维数组 result :记录每一条路径的最终结果 r++; path[0] = 0; // 将第 0 个位置重新置为 0(表示恢复之前的状态) return; } path[p++] = 0; // 向左走路径标记为 0 HuffmanEncode(ht, ht[root].Lchild); // 递归到左节点 p--; // 从左节点回退回来:p-- 代表将 path[p++] = 0; 这里曾经标记的 0 给"删除" path[p++] = 1; // 路径标记为 1 HuffmanEncode(ht, ht[root].Rchild); // 递归到右节点 p--; // 从右节点回退回来:p-- 代表将 path[p++] = 1; 这里曾经标记的 1 给"删除" }
主函数 Main.c
int main() { // 输入 m 和 w 数组 int m, w[MAX]; scanf("%d", &m); float t = 0; for (int i = 0; i < m; ++i) { scanf("%f", &t); w[i] = t * 100; // 浮点树先乘上倍数,变成整型,方便计算 } // 构建一棵树 PHtTree* pht = CreateHuffMan(m, w); HuffmanEncode(pht->ht, pht->root - 1); // 传数组过去就可以了 // 打印每一编码 printf("\n"); for (int i = 0; i < m; ++i) { int x = result[i][0]; for (int j = 1; j <= x; ++j) { printf("%d ", result[i][j]); } printf("\n"); } return 0; }