哈夫曼树与哈夫曼编码,c/c++描述

  举例如在报文通信中,报文内容转换成对应的0和1组成的编码来发送和接收。显然,对于同一报文,总的编码长度越短越好。那么我们怎么设计报文的编码呢?ascii码是一种等长编码,每个字符对应7个bit位。也可以另设计变长编码,不同字符对应的编码长度不等,原则是报文中出现频率高的字符编码短一些,出现频率低的字符可以编码长一些。这种编码就叫哈夫曼编码,为了纪念和表彰伟大的科学家。因为哈夫曼编码的报文长度最短。
  可以把报文中的字符作为二叉树的叶节点,字符的出现频率作为节点的权值。从根节点到叶节点的路径长度乘以叶节点权值,叫做该叶节点的带权路径长度,所有叶节点的带权路径长度之和,叫做该二叉树的带权路径长度。权值也可以全为1,相当于节点不带权值。同一批一模一样的叶节点,可以对应多个不同的二叉树,其中具有最小带权路径长度的二叉树叫做哈夫曼树,也叫最优二叉树,其编码规则是权值小的叶节点远离根节点,权值大的叶节点接近根节点。
  建立哈夫曼树的具体过程就是,以所有叶节点构成一个节点的集合,从节点集中找出权值最小的俩节点,并构造其父母节点,其父母节点的权值等于这俩孩子节点的权值之和,并从节点集合中删除这俩叶节点,添加进其父母节点。如此循环,直至节点集合中只剩下一个节点,即为建立的二叉树的根节点。
  代码跟课本上的基本差不多,但改进了变量和函数命名,见名知意,易于理解。
  注意:虽然说是二叉树,但节点并没有指针成员,所有指针成员换成了存储节点的数组的下标,借助存储二叉树所有节点的数组的下表来找到所需要的节点,如孩子节点或父母节点。
  按照左小右大的规则,左子节点的权值较小,右子节点的权值较大;从父母节点到左子节点的路径用0表示,从父母节点到右子节点的路径用1表示;从根节点到叶节点的路径用0和1表示出来,即是该叶节点对应的哈夫曼编码,如此编码,可使报文长度最短。当然我们是从叶节点开始找到根节点的,得到的路径编码跟哈夫曼编码是反着的,借助中间数组倒一下顺序即可。
  寻找最小和次最小节点(注意:并非全是叶节点,也可以包括分支节点)的方法也很巧妙,相当于已知两个已排序的数,和第三个数的大小做比较,并重新找出最小和次小的数。第三个数可能是最小的,也可能权值大小居于中间,也可能权值三个数中最大的。代码即是以此思路写的。
  好的代码一定是易于理解的代码,不好让别人理解的代码一定是需要该改进的代码。课本的例题说实话最近是一道也没有看懂,可能是思路太奇葩,也因为变量命名没有见名知意,多参考bilibili站的优秀视频才能学会老师的代码,参考后再自己动手写。非常打击学习的动力和自信心。
  到此,哈夫曼编码内容就结束了,其实也不复杂,整的故弄玄虚,高深莫测,让别人看不懂,学不会,就不好了。以后我会收集一本最详细易懂的教材,作为范本储存,知识忘了再拿出来翻一翻。现在继续痛苦着,还有130页,这本书就结束了。
  源代码如下:

#include<iostream>
using namespace std;
#define MAXLEAF 50
#define MAXTREE 2 * MAXLEAF - 1
struct HuffmanTreeNode {
	char data;
	int weight;
	int leftChild;
	int rightChild;
	int parent;
};
struct HuffmanCode {
	char codes[10];   //10是任意给的,数组够大即可
	int codesLength;
};

void createHuffmanTree(HuffmanTreeNode nodes[],int leafNum) {//假设叶节点权值已有序排列
	int nodesTotal = 2 * leafNum - 1;//从哈夫曼树的构造过程即可得到,每添加一个叶节点,同时生成
	int weightMin, weightMinor, indexMin = -1, indexMinor = -1;//一个分支节点
	int indexParent, indexChild;
	
	for (indexParent = leafNum; indexParent < nodesTotal; indexParent++) {
		weightMin = weightMinor = 32767;//该值够大即可
		indexMin = -1, indexMinor = -1;

		for(indexChild = 0 ; indexChild < indexParent ; indexChild++)
			if (nodes[indexChild].parent == -1) 
				if (weightMin > nodes[indexChild].weight) {
					weightMinor = weightMin;
					indexMinor = indexMin;

					weightMin = nodes[indexChild].weight;
					indexMin = indexChild;
				}
				else if (nodes[indexChild].weight < weightMinor) {
					weightMinor = nodes[indexChild].weight;
					indexMinor = indexChild;
				}
			
		nodes[indexParent].weight = weightMin + weightMinor;
		nodes[indexParent].leftChild = indexMin;
		nodes[indexParent].rightChild = indexMinor;
		
		nodes[indexMin].parent = indexParent;
		nodes[indexMinor].parent = indexParent;
	}
}

void createHuffmanCode(HuffmanTreeNode nodes[],HuffmanCode codes[],int leafNum) {
	char temp[10];
	int codesLength = 0,indexChild,indexParent;

	for (int i = 0; i < leafNum; i++) {
		indexChild = i;
		codesLength = 0;

		while (nodes[indexChild].parent != -1) {//-1表明该节点尚未分配父母节点,未进入二叉树
			indexParent = nodes[indexChild].parent;
			if (nodes[indexParent].leftChild == indexChild)
				temp[codesLength] = '0';//temp数组是一个过渡数组,存储了哈夫曼编码的反序
			else
				temp[codesLength] = '1';
			codesLength++;
		
			indexChild = indexParent;
		}
		codes[i].codesLength = codesLength;
		codesLength--;
		for (int j = 0; codesLength > -1; codesLength--) {
			codes[i].codes[j] = temp[codesLength];
			j++;
		}
	}
}

void diasplay(HuffmanTreeNode nodes[], HuffmanCode codes[], int leafNum) {
	int i, j;
	for (i = 0; i < leafNum; i++) {
		cout << nodes[i].data << "     ";
		for (j = 0; j < codes[i].codesLength; j++)
			cout << codes[i].codes[j];
		cout << endl;
	}
}

int main() {
	const int leafNum = 15;
	char leafData[leafNum] = {'a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
							'k', 'l', 'm', 'n', 'o'};
	int leafWeight[leafNum] = {1192,677,541,518,462,450,242,195,190,181,174,157,
								138,124,123};
	
	HuffmanTreeNode nodes[MAXTREE];//对二叉树节点数组的初始化
	for (int i = 0; i < leafNum; i++) {
		nodes[i].data = leafData[i];
		nodes[i].weight = leafWeight[i];
	}
	for (int i = 0; i < 2 * leafNum - 1; i++)
		nodes[i].leftChild = nodes[i].rightChild = nodes[i].parent = -1;

	createHuffmanTree(nodes,leafNum);
	
	HuffmanCode codes[leafNum];
	createHuffmanCode(nodes,codes,leafNum);
	cout << "char and its Huffman-codes is as follows : " << endl;
	diasplay(nodes, codes, leafNum);

	return 0;
}

  我还有个感悟是:编程里对同一问题的解决思路和答案是多种多样的,更灵活,不要拘泥一格,奉课本为标准答案,只要你能有自己思路,解决了问题即可,在课本基础上改进,也算的。因为编程毕竟不是数学,编程比数学的多种解法,还要丰富多彩,精彩纷呈。因为编程没有数学那么严谨,没有数学定理真理那么多条条框框,所以自由发挥的余地更大。有高中的数学基础在大部分情况下就够使。即是如此,你的才华也要接受多种多样新事物新思路的考验。
  课本代码里,数组名叫hcd,给我整不会了,其意思应该是huffmanCode,存储节点对应的哈夫曼编码,刚开始时我一度联想到了欧几里得辗转相除法里有这么个hcd变量名或方法名,此处又代表什么意思呢?严重影响看懂课本代码。半页长度的代码都看不懂。
测试结果如下,和课本里的结果是一样的。
在这里插入图片描述
谢谢阅读。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值