哈夫曼编码


题目:要传输一则报文内容如下:
“AAAAAAAAAAAAAAABBBBBBBBBCCCCCCCCDDDDDDDDDDDDEEEEEEEEEEFFFFF”
请为这段报文设计哈夫曼编码,要求如下:

  1. 请计算出每个字符出现的概率,并以概率为权重来构造哈夫曼树,写出构造过程、画出最终的哈夫曼树,得到每个字符的哈夫曼编码。
  2. 请将上述设计哈夫曼编码的过程,用代码来实现,并输出各个字母的哈夫曼编码。(有代码,有运行结果的截图)
  3. 请分析算法的效率,至少包括时间复杂度和空间复杂度等。

一、构造过程
① 统计可知,字符串全长共59个字符,其中字符‘A’-‘F’的出现频数及对应概率(保留两位小数)如图所示:

②将每个字符出现概率作为权重,则对于上述给定的6个权值{25,15,14,20,17,9},依次构造6棵只有根节点的二叉树,共同构成森林F;

③在森林F中,选取权重最小和次小的两颗树,分别作为新二叉树的左子树、右子树,且该树根节点权值赋左右子树权值之和;

④从森林F中删除已选中的两棵子树,把新得到的二叉树加入F中;

⑤重复③④,直到森林F中仅留下一棵树时,得到最终的哈夫曼树HF如图所示:

⑥对上述哈夫曼树,将其树中的每个左分支赋0、右分支赋1,则从根结点开始到叶子结点,各分支路径分别得到对应的二进制串,即为给定的每个字符的哈夫曼编码。

二、代码实现

编译环境:Visual Studio 2019 16.6.3,代码如下:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 6 //指定的编码字符数
#define M (2 * N - 1) //HT结点数
#define MAXWEIGHT 100
typedef struct { //哈夫曼树的存储表示
	int weight;
	int parent, lchild, rchild;
}HTNode;
typedef HTNode HuffmanTree[M + 1];
typedef char* HuffmanCode[N + 1]; //哈夫曼编码表
void Select(HuffmanTree HT, int n, int& s1, int& s2);
void CreateHuffmanTree(HuffmanTree& HT, int* w, int n);
void CreateHuffmanCode(HuffmanTree HT, HuffmanCode& HC);
void PrintHC(HuffmanCode HC, char ch[]);

int main() {
	HuffmanTree HT;
	HuffmanCode HC;
	int w[N + 1];
	char ch[N + 1];

	printf("Please input %d characters & its weight(e.g. A 25):\n", N);
	for (int i = 1; i <= N; i++) {
		scanf("%c %d", &ch[i], &w[i]);
		getchar();
	}

	CreateHuffmanTree(HT, w, N);
	CreateHuffmanCode(HT, HC);
	PrintHC(HC, ch);

	return 0;
}

void Select(HuffmanTree HT, int n, int& s1, int& s2) {
	int i, min = 0, temp = MAXWEIGHT;
	for (i = 1; i <= n; i++) {
		if (!HT[i].parent) {
			if (temp > HT[i].weight) {
				temp = HT[i].weight;
				min = i; //最小值作为新树左子树
			}
		}
	}
	s1 = min;
	for (i = 1, min = 0, temp = MAXWEIGHT; i <= n; i++) {
		if ((!HT[i].parent) && i != s1) {
			if (temp > HT[i].weight) {
				temp = HT[i].weight;
				min = i; //次小值作为新树右子树
			}
		}
	}
	s2 = min;
}

void CreateHuffmanTree(HuffmanTree& HT, int* w, int n) { //创建哈夫曼树HT
	int i, s1, s2;
	if (n <= 1) return; //树为空或仅有根节点
	for (i = 1; i <= M; ++i) { //初始化
		HT[i].weight = w[i];
		HT[i].parent = 0;
		HT[i].lchild = 0;
		HT[i].rchild = 0;
	}
	for (i = n + 1; i <= M; ++i) { //创建新二叉树
		Select(HT, i - 1, s1, s2);
		HT[s1].parent = i;
		HT[s2].parent = i;
		HT[i].lchild = s1;
		HT[i].rchild = s2;
		HT[i].weight = HT[s1].weight + HT[s2].weight;
	}
}

void CreateHuffmanCode(HuffmanTree HT, HuffmanCode& HC) {
	int i, start, c, f;
	char cd[N];
	cd[N - 1] = '\0'; //编码结束符
	for (i = 1; i <= N; i++) { //对于第i个待编码字符即第i个带权值的叶子节点
		start = N - 1; //开始start指向结束符位置
		c = i;
		f = HT[i].parent; //f指向当前结点的双亲
		while (f) { //从叶上溯,到根节点跳出
			if (HT[f].lchild == c)
				cd[--start] = '0'; //左孩子
			else
				cd[--start] = '1'; //右孩子
			c = f;
			f = HT[f].parent;
		}
		HC[i] = new char[N - start];
		strcpy(HC[i], &cd[start]); //复制结果到编码表,用于输出
	}
}

void PrintHC(HuffmanCode HC, char ch[]) { //打印哈夫曼编码表
	for (int i = 1; i <= N; i++)
		printf("\n%c: %s\n", ch[i], HC[i]);
}

输出各个字母的哈夫曼编码:

三、算法分析
1、哈夫曼编码是最优前缀编码:对包括N个字符的数据文件,分别以它们的出现概率构造哈夫曼树,利用该树对应的哈夫曼编码对报文进行编码,得到压缩后的最短二进制编码;
2、算法自底而上地构造出对应最优编码的二叉树HT,它从n个叶子结点开始,识别出最低权重的两个对象,并将其合并;当合并时,新对象的权重设置为原两个对象权值之和,一共执行了|n| - 1次合并操作;
3、是贪心算法:每次选择均为当下的最优选择,通过合并来构造最优树得到对应的哈夫曼编码;
4、复杂度:
①时间复杂度:有n个终端节点,构成的哈夫曼树总共有2n-1个节点,Select函数的时间复杂度为O(n),嵌套for循环,因此建立哈夫曼树的时间复杂度为O(n^2), 创建哈弗曼编码表的时间复杂度为O(n^2);
②空间复杂度:算法中临时占用存储空间大小与初始森林F中树的个数n线性相关,n越大问题规模也越大,其耗费的空间复杂度为O(n)。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM