6.6.2 赫夫曼编码


  • 正则二叉树 / 严格二叉树
    没有度为1的结点的树。

  • 赫夫曼树就是一种正则二叉树 / 严格二叉树

报文编码问题

  • 电报 : 将要传送的文字转换成二进制数串
    如电文中包含四种字符 { A , B , C , D },那么只需要两位二进制数即可完成编码。

  • 在传送电报时,总是希望总长尽可能短。
    如果每个字符出现频率相同,那么对每个字符采用一样的编码位数对文本总长无影响。
    如果每个字符出现频率不同,那么可以对出现频率高的字符采用较短位数的编码,对出现频率低的字符的编码位数可以多一些。

  • 前缀编码
    有这么一种编码方式,任意一个字符的编码都不是另一个字符的编码的前缀,叫做前缀编码。

  • 可以利用二叉树设计二进制的前缀编码
    因为到二叉树的叶子结点的路径 必不可能是 到另一个叶子结点的路径。所以不存在前缀关系。

  • 利用二叉树来获取报文字符 { A , B , C , D } 的报文最短编码 (赫夫曼码)
    在这里插入图片描述

  • 如上图,设叶子结点的权值就是对应报文字符在报文中出现的次数
    那么 电文总长 就是上述二叉树的带权路径长度 ∑ Wi * Li,也就是报文编码的总长度

  • 赫夫曼编码 / 最优前缀编码
    设计一棵赫夫曼树,由此得到的二进制前缀编码的方式。

具体做法

  • 赫夫曼树中没有度为1的结点,是一种正则二叉树,因此一棵有n个叶子结点的赫夫曼树共有2n-1个结点。
    编码需要从叶子结点出发,找到一条从叶子到根的路径
    译码需要从根结点出发,找到一条从根到叶子的路径
    因此,对于每个结点而言,既需要知道双亲的信息,又需要知道孩子结点的信息。
    由此引出结点的存储结构,如下
typedef struct
{
	// 结点数据域 存储 结点权重
	unsigned int weight; 
	// 父母结点下标,左子树下标,右子树下标(在顺序表中的下标)
	unsigned int parent, lchild, rchild;
} HTNode, *HuffmanCode;

typedef char* *HuffmanCode;

// 求报文赫夫曼编码的算法
void HuffmanCoding(HuffmanTree &HT, HuffmanCode &HC, int *w, int n)
{
	// w 存放n个报文字符的权重值. HT 为已知的赫夫曼树
	// HC 为字符数组,存放赫夫曼树的叶子结点的编码
	if(n <= 1)
	{
		return;
	}
	m = 2 * n - 1;
	// 0号单元弃用
	HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode));
	// HT 的 1到n个位置 用来存放(叶子)结点,初始化它们的权重
	for(p - HT, i = 1;
			i <= n;
			i++, p++, w++)
	{
		*p = { *w, 0, 0, 0 };
	}
	// HT 剩下来的结点用来存放遍历过程中产生的分支结点
	for(; i <= m; i++, p++)
	{
		*p = { 0, 0, 0, 0 };
	}
	// 建立赫夫曼树
	for(i=n+1; i<=m; i++)
	{
		// 在 [1, n-1] 选择 parent 为 0 且 weight 最小的两个结点
		// 结点序号存入 s1, s2
		Select(HT, i-1, s1, s2);
		HT[s1].parent = i;
		HT[s2].parent = i;
		// 从 HT[n+1] 开始,依次存放新建分支结点的信息
		// HT 最后一个单元表示根结点
		HT[i].lchild = s1;
		HT[i].rchild = s2;
		HT[i].weight = HT[s1].weight + HT[s2].weight;
	}
	// HC 是一个字符串数组,用于存放n个叶子结点编码串的n个起始地址
	HC = (HuffmanCode)malloc((n+1) * sizeof(char *));
	// 分配求编码的工作空间
	cd = (char *)malloc(n * sizeof(char));
	// 表示'\0' ,编码结束符
	cd[n-1] = 0;
	// 逐个字符求赫夫曼编码,
	// 从叶子结点开始逆向求赫夫曼编码
	for(i=1; i<=n; i++)
	{
		start = n-1;
		// 以下实现一个叶子结点到根结点的路径
		for(c=i, f=HT[i].parent; 
				f!=0;    //只有根结点的parent域为0
				c=f, f=HT[f].parent )
		{
			if(HT[f].lchild == c)
			{
				cd[--start] = '0';
			}
			else
			{
				cd[--start] = '1';
			}
			// 根据编码位数分配空间存储赫夫曼编码
			HC[i] = (char *)malloc((n-start) * sizeof(char));
			// 从工作区间 复制到 编码数组里面
			strcpy(HC[i], &cd[start]);
		}
	}
}
  • 无栈非递归遍历赫夫曼树。
typedef struct
{
	unsigned int weight;
	unsigned int parent, lchild, rchild;
} HTNode, *HuffmanCode;

typedef char* *HuffmanCode;
void HuffmanCoding(HuffmanTree &HT, HuffmanCode &HC, int *w, int n)
{
	typedef struct
{
	unsigned int weight;
	unsigned int parent, lchild, rchild;
} HTNode, *HuffmanCode;

typedef char * *HuffmanCode;

void HuffmanCoding(HuffmanTree &HT, HuffmanCode &HC, int *w, int n)
{
	if(n <= 1)
	{
		return;
	}
	// n个叶子结点 和 n-1个度为2的结点
	m = 2 * n - 1;
	// 0号单元弃用,下面从1号单元开始存放
	HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode));
	for(p = HT+1, i = 1;
			i <= n;
			i++, p++, w++)
	{
		// 1到n号位置存放n个叶子结点
		*p = { *w, 0, 0, 0 };
	}
	// 剩下的n-1个空位存放遍历过程中产生的分支结点,到最终的根结点
	for(; i <= m; i++, p++)
	{
		*p = { 0, 0, 0, 0 };
	}
	// 从n+1号位置开始,依次放入遍历过程产生的分支结点
	// m号位置也就是第m+1个单元存放根结点
	for(i=n+1; i<=m; i++)
	{
		// 从 1 到 i-1 号单元中选出权值最小的两个结点,
		// 下标赋值给 s1,s2
		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;
	}
	// HC 存放 n个叶子结点编码串的n个起始地址
	// 第 n+1 个存放'\0',表示编码结束符
	HC = (HuffmanCode)malloc((n+1) * sizeof(char *));
	p = m;
	cdlen = 0;
	// 清空上面产生的m个结点的weight域
	// 结点的weight域将作为遍历时结点的状态标志
	for(i=1; i<=m; i++)
	{
		HT[i].weight = 0;
	}
	// 从根结点到叶子结点 
	// 开始遍历,产生编码
	while(p)
	{
		if(HT[p].weight == 0) // 0表示接下来访问结点左子树
		{
			// 访问完左子树,weight为1,
			// 表示 下一次遇到该结点访问右子树
			HT[p] = weight = 1; 
			if(HT[p].lchild != 0)
			{
				// 左子树存在,访问左子树,
				// 该步编码值为 0
				p = HT[p].lchild;
				cd[cdlen++] = "0";
			}
			else if(HT[p].rchild == 0)
			{
				// 左子树不存在,且右子树不存在
				// 成功到达一个叶子结点,完成一个结点的编码
				HC[p] = (char *)malloc((cdlen + 1) * sizeof(char));
				cd[len] = "\0";
				strcpy(HC[p], cd);
			}
		}
		// 为1 表示已访问完左子树,此时该访问右子树
		else if(HT[p].weight == 1) 
		{
			HT[p].weight = 2;
			// 右子树不为0,则访问右子树根结点
			// 此步码值为 1 
			if(HT[p].rchild != 0)
			{
				p = HT[p].rchild;
				cd[cdlen++] = "1";
			}
		}
		else
		{
			// 为2的情况,表示左右子树都已经访问完
			// 开始回退一步
			HT[p].weight = 0;
			p = HT[p].parent;
			--cdlen;
		}
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值